effective_search 0.2.3 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75a1014aa1a88849dda458a6b59ae0401bd9b32a6ecd1573d2359cab1ec23bac
4
- data.tar.gz: 446831390104f3bd59bdee99fbd5f88d9e5bb852894ffc1175fad1796d7adb80
3
+ metadata.gz: a5bc0454808d5c92f57c4d8b5df6aec31c7c3a9d0b278c1824722c1a7598ffa2
4
+ data.tar.gz: fd62ab6ab6b636122745dda9053521f18f7c8fe6462cf2485b39ca5998a9b3ad
5
5
  SHA512:
6
- metadata.gz: e196d2505d2ccf6181a4359d564c154898eec5923ef2a9ef634aa8dde2cce89f15a9238587785ddaeb258da8fc16f4e17f8559c8cc53427899d23821d986446a
7
- data.tar.gz: 54586b1cf7043d0026542fb63d51cd42377ece606b14cdb6bd2cc0aa649edaf5a3fa803bdeea02b49dbe41365e3ddb3b255bb854cc00a7d6074ab367ddcecf36
6
+ metadata.gz: 5e359cf8e8ce4d0bd34941b3b7a7bb3530af955ef6fe461cec81e21e29000ef6f6781ecc4265c506984851a6e52856e50381531b9a95a973e834fc28a02b31a2
7
+ data.tar.gz: cfc31d57f4d4ab5200298730ee1da92def9cd672220d6156ea4ac72c72840b14e0f8f233082092329c70a74d99bb6cadc2fa9546348d502dc2de5932c1c3fddd
@@ -0,0 +1,62 @@
1
+ // Admin search handle pagination clicks
2
+ $(document).on('click', '#effective-search-admin-modal .page-link', function(event) {
3
+ event.preventDefault();
4
+
5
+ var $link = $(this);
6
+ var href = $link.attr('href');
7
+
8
+ // Extract the page parameter from the URL
9
+ var pageMatch = href.match(/[?&]page=(\d+)/);
10
+ if (!pageMatch) return;
11
+
12
+ var pageNumber = pageMatch[1];
13
+
14
+ // Find the closest form and update the hidden page field
15
+ var $form = $link.closest('form');
16
+ if ($form.length === 0) return;
17
+
18
+ var $pageField = $form.find('input[name="page"]');
19
+ if ($pageField.length === 0) return;
20
+
21
+ // Update the page value and submit the form
22
+ $pageField.val(pageNumber);
23
+
24
+ $form.find("[data-load-ajax-url]").trigger('reload');
25
+ });
26
+
27
+ // Admin search close the modal when they visit a search result page
28
+ $(document).on('click', '#effective-search-admin-modal a', function(event) {
29
+ var $link = $(event.currentTarget);
30
+ if($link.hasClass('page-link') || $link.data('effective-search-clear')) return;
31
+
32
+ $('#effective-search-admin-modal').modal('hide');
33
+ });
34
+
35
+ // Admin search clear button
36
+ $(document).on('click', '#effective-search-admin-modal a[data-effective-search-clear]', function(event) {
37
+ event.preventDefault();
38
+
39
+ var $form = $(event.currentTarget).closest('form')
40
+
41
+ $form.trigger('reset');
42
+ $form.find('#effective-search-results').html('');
43
+ $form.find('input[name="page"]').val(1);
44
+ $form.find('input[name="q[term]"]').focus();
45
+ });
46
+
47
+ // Admin search reset to page 1 when search term changes
48
+ $(document).on('keyup paste', "#effective-search-admin-modal [data-load-ajax-url]", function(event) {
49
+ var $form = $(event.currentTarget).closest('form')
50
+ $form.find('input[name="page"]').val(1);
51
+ });
52
+
53
+ // Prevent form submission on Enter key press
54
+ $(document).on('keydown keyup', "#effective-search-admin-modal form", function(event) {
55
+ if(event.key === 'Enter') { event.preventDefault(); }
56
+ });
57
+
58
+ // Auto focus the search input when the modal is shown
59
+ $(document).on('shown.bs.modal', '#effective-search-admin-modal', function () {
60
+ $(".effective-search-admin").find('input[type="search"]').first().focus();
61
+ });
62
+
@@ -1,11 +1,7 @@
1
- $(document).on('turbolinks:load', function() { initializeSearchCollapse(); });
1
+ $(document).on('shown.bs.collapse', '#effective-search-icon-form', function () {
2
+ $("#effective-search-input").focus();
3
+ });
2
4
 
3
- function initializeSearchCollapse() {
4
- $('#effective-search-icon-form').on('shown.bs.collapse', function () {
5
- $("#effective-search-input").focus()
6
- })
7
-
8
- $('#effective-search-icon-form').on('hidden.bs.collapse', function () {
9
- $("#effective-search-input").blur()
10
- })
11
- }
5
+ $(document).on('hidden.bs.collapse', '#effective-search-icon-form', function () {
6
+ $("#effective-search-input").blur();
7
+ });
@@ -0,0 +1,14 @@
1
+ .effective-search-admin {
2
+ #effective-search-results {
3
+ a {
4
+ display: block;
5
+ color: inherit;
6
+ text-decoration: none;
7
+ }
8
+
9
+ span.badge {
10
+ color: #1F2328;
11
+ background-color: #EDEEEF;
12
+ }
13
+ }
14
+ }
@@ -1 +1,2 @@
1
1
  @import 'effective_search/base';
2
+ @import 'effective_search/admin';
@@ -0,0 +1,36 @@
1
+ module Admin
2
+ class SearchController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_search) }
5
+
6
+ include Effective::CrudController
7
+
8
+ def index
9
+ EffectiveResources.authorize!(self, :index, EffectiveSearch.AdminSearch)
10
+
11
+ # But more often we do a full membership directory search screen
12
+ @search = build_search
13
+ @search.search!
14
+
15
+ @page_title = "Search"
16
+ end
17
+
18
+ def build_search
19
+ search = EffectiveSearch.AdminSearch.new()
20
+ search.assign_attributes(search_params.select { |k, _| search.respond_to?("#{k}=") })
21
+ search.assign_attributes(current_user: current_user, view_context: view_context)
22
+ search
23
+ end
24
+
25
+ def search_params
26
+ return {} unless params[:q].present?
27
+
28
+ if params[:q].respond_to?(:to_h) # From the search form
29
+ params.require(:q).permit!
30
+ else
31
+ { term: params.permit(:q).fetch(:q) } # From the url /directory?q=asdf
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -12,4 +12,10 @@ module EffectiveSearchHelper
12
12
  end
13
13
  end
14
14
 
15
+ def effective_search_admin_nav_icon
16
+ if EffectiveResources.authorized?(self, :index, EffectiveSearch.AdminSearch)
17
+ render('admin/search/form_nav_icon')
18
+ end
19
+ end
20
+
15
21
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EffectiveSearchAdminSearch
4
+ # Mark your search model with include EffectiveSearchAdminSearch
5
+
6
+ module EffectiveSearchAdminSearch
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def effective_search_admin_search?; true; end
11
+ end
12
+
13
+ included do
14
+ include ActiveModel::Model
15
+
16
+ attr_accessor :current_user
17
+ attr_accessor :view_context
18
+
19
+ attr_accessor :term
20
+ validates :term, length: { minimum: 1, allow_blank: true }
21
+ end
22
+
23
+ def to_s
24
+ term.to_s
25
+ end
26
+
27
+ def per_page
28
+ 3
29
+ end
30
+
31
+ def present?
32
+ term.present?
33
+ end
34
+
35
+ def users
36
+ @users ||= Effective::Resource.new(current_user.class).search_any(term, columns: [:first_name, :last_name, :email])
37
+ end
38
+
39
+ def organizations
40
+ @organizations ||= Effective::Resource.new(EffectiveMemberships.Organization).search_any(term, columns: [:title, :email])
41
+ end
42
+
43
+ def orders
44
+ @orders ||= Effective::Resource.new(Effective::Order).search_any(term, columns: [:id])
45
+ end
46
+
47
+ def applicants
48
+ @applicants = EffectiveMemberships.Applicant.where(user: users).order(created_at: :desc)
49
+ end
50
+
51
+ # Search and assigns the collection
52
+ # Assigns the entire collection() if there are no search terms
53
+ # Otherwise validate the search terms
54
+ def search!
55
+ @users = users()
56
+ @orders = orders()
57
+ @applicants = applicants()
58
+ @organizations = organizations()
59
+ end
60
+
61
+ # The paginated results
62
+ def user_results(page: nil)
63
+ results(users, page: page)
64
+ end
65
+
66
+ def order_results(page: nil)
67
+ results(orders, page: page)
68
+ end
69
+
70
+ def applicant_results(page: nil)
71
+ results(applicants, page: page)
72
+ end
73
+
74
+ def organization_results(page: nil)
75
+ results(organizations, page: page)
76
+ end
77
+
78
+ private
79
+
80
+ def results(collection, page: nil)
81
+ page = (page || 1).to_i
82
+ offset = [(page - 1), 0].max * per_page
83
+
84
+ collection.limit(per_page).offset(offset)
85
+ end
86
+
87
+ end
88
+
@@ -0,0 +1,5 @@
1
+ module Effective
2
+ class AdminSearch
3
+ include EffectiveSearchAdminSearch
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ %tr
2
+ %td
3
+ %a{href: effective_memberships.edit_admin_applicant_path(applicant), title: applicant}
4
+ %span.badge.rounded-circle.p-2.mr-3.float-left= icon('clipboard')
5
+ = applicant.owner
6
+ %br
7
+ %small.text-muted
8
+ = applicant.summary_title
9
+ = ' &bull; '.html_safe
10
+ = applicant.owner&.email
11
+ = ' &bull; '.html_safe
12
+ = badge(applicant.status)
@@ -0,0 +1,3 @@
1
+ %li.nav-item.effective-search-nav-item
2
+ %a.nav-link{href: '#', data: { toggle: 'modal', target: '#effective-search-admin-modal' }, title: 'Search'}
3
+ = icon('search')
@@ -0,0 +1,2 @@
1
+ .effective-search-admin
2
+ = yield
@@ -0,0 +1,8 @@
1
+ %div#effective-search-admin-modal.modal.fade{tabindex: '-1', role: 'dialog', 'data-turbolinks-permanent': true}
2
+ .modal-dialog.modal-lg{role: 'document'}
3
+ .modal-content
4
+ .modal-body
5
+ - search = EffectiveSearch.AdminSearch.new
6
+ = render('admin/search/search', search: search, modal: true)
7
+ .modal-footer.bg-light.p-1
8
+ %small.text-muted Search by name, email, #{etd(EffectiveMemberships.Organization)}, or order number.
@@ -0,0 +1,16 @@
1
+ %tr
2
+ %td
3
+ - path = (EffectiveResources.authorized?(self, :edit, order) ? effective_orders.admin_edit_order_path(order) : effective_orders.admin_order_path(order))
4
+
5
+ %a{href: path, title: order}
6
+ %span.badge.rounded-circle.p-2.mr-3.float-left= icon('shopping-cart')
7
+ = order.to_s
8
+ %br
9
+ %small.text-muted
10
+ = order.billing_name
11
+ = ' &bull; '.html_safe
12
+ = order.email
13
+ = ' &bull; '.html_safe
14
+ = price_to_currency(order.total)
15
+ = ' &bull; '.html_safe
16
+ = badge(order.status)
@@ -0,0 +1,28 @@
1
+ %tr
2
+ %td
3
+ %a{href: effective_memberships.edit_admin_organization_path(organization), title: organization}
4
+ %span.badge.rounded-circle.p-2.mr-3.float-left= icon('globe')
5
+ = organization
6
+ %br
7
+ %small.text-muted
8
+ - if organization.class.try(:effective_memberships_organization_owner?)
9
+ - if (membership = organization.try(:membership)).present?
10
+ = membership.statuses.to_sentence
11
+ = membership.categories.to_sentence
12
+ member
13
+ - if membership.number.present?
14
+ = ' #' + membership.number
15
+ - elsif organization.membership_removed?
16
+ Removed member
17
+ - else
18
+ Not a member
19
+ = ' &bull; '.html_safe
20
+
21
+ = organization.email
22
+
23
+ - if (principals = organization.representatives.select { |rep| rep.is_any?(:principal) }).present?
24
+ = ' &bull; '.html_safe
25
+ = principals.to_sentence
26
+
27
+ = ' &bull; '.html_safe
28
+ = pluralize(organization.representatives.count, 'representative')
@@ -0,0 +1,41 @@
1
+ = render 'admin/search/layout' do
2
+
3
+ = effective_form_with(scope: :q, model: search, method: :get, url: effective_search.admin_search_path) do |f|
4
+ = hidden_field_tag :page, (params[:page] || 1)
5
+
6
+ .d-flex.align-items-center
7
+ .flex-grow-1
8
+ = f.search_field :term, placeholder: 'Search', label: false, autofocus: true, autocomplete: 'off',
9
+ 'data-load-ajax-url': effective_search.admin_search_path,
10
+ 'data-load-ajax-div': '#effective-search-results',
11
+ 'data-load-ajax-all': true
12
+
13
+ - if modal
14
+ %div
15
+ = link_to 'Clear', '#', class: 'btn-link', style: "position: absolute; top: 1.5rem; right: 4rem; z-index: 10;", 'data-effective-search-clear': true
16
+
17
+ %div.ml-4.mb-3
18
+ %button.close{type: 'button', data: { dismiss: 'modal' }, 'aria-label': 'Close'}
19
+ %span{'aria-hidden': 'true'} &times;
20
+
21
+ #effective-search-results
22
+ - if search.present?
23
+ - # Users
24
+ %p Users (#{search.users.count})
25
+ %table.table.table-hover
26
+ %tbody= render(collection: search.user_results, partial: 'admin/search/user')
27
+
28
+ - # Organizations
29
+ %p #{ets(search.organizations)} (#{search.organizations.count})
30
+ %table.table.table-hover
31
+ %tbody= render(collection: search.organization_results, partial: 'admin/search/organization')
32
+
33
+ - # Applicants
34
+ %p Applicants (#{search.applicants.count})
35
+ %table.table.table-hover
36
+ %tbody= render(collection: search.applicant_results, partial: 'admin/search/applicant')
37
+
38
+ - # Orders
39
+ %p Orders (#{search.orders.count})
40
+ %table.table.table-hover
41
+ %tbody= render(collection: search.order_results, partial: 'admin/search/order')
@@ -0,0 +1,24 @@
1
+ %tr
2
+ %td
3
+ %a{href: edit_admin_user_path(user), title: user}
4
+ %span.badge.rounded-circle.p-2.mr-3.float-left= icon('user')
5
+ = user.to_s
6
+ %br
7
+ %small.text-muted
8
+ - if(membership = user.try(:membership) || user.try(:organizations).try(:first).try(:membership)).present?
9
+ = membership.statuses.to_sentence
10
+ = membership.categories.to_sentence
11
+ member
12
+ - if membership.number.present?
13
+ = ' #' + membership.number
14
+ - elsif user.membership_removed?
15
+ Removed member
16
+ - else
17
+ Not a member
18
+ = ' &bull; '.html_safe
19
+
20
+ = user.email
21
+
22
+ - if user.try(:representatives).present?
23
+ = ' &bull; '.html_safe
24
+ = user.representatives.map(&:organization).to_sentence
@@ -0,0 +1 @@
1
+ = render('admin/search/search', search: @search, modal: false)
data/config/routes.rb CHANGED
@@ -11,6 +11,7 @@ EffectiveSearch::Engine.routes.draw do
11
11
  end
12
12
 
13
13
  namespace :admin do
14
+ match '/search', to: 'search#index', via: [:get], as: :search
14
15
  resources :search_contents, except: [:show]
15
16
  end
16
17
 
@@ -1,3 +1,3 @@
1
1
  module EffectiveSearch
2
- VERSION = '0.2.3'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -5,7 +5,7 @@ require 'effective_search/version'
5
5
  module EffectiveSearch
6
6
 
7
7
  def self.config_keys
8
- [:search_class_name, :search_contents_table_name, :authenticate_user, :layout]
8
+ [:search_class_name, :admin_search_class_name, :search_contents_table_name, :authenticate_user, :layout]
9
9
  end
10
10
 
11
11
  include EffectiveGem
@@ -14,6 +14,10 @@ module EffectiveSearch
14
14
  search_class_name&.constantize || Effective::Search
15
15
  end
16
16
 
17
+ def self.AdminSearch
18
+ admin_search_class_name&.constantize || Effective::AdminSearch
19
+ end
20
+
17
21
  def self.authenticate_user?
18
22
  authenticate_user == true
19
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-24 00:00:00.000000000 Z
11
+ date: 2026-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -162,16 +162,30 @@ files:
162
162
  - Rakefile
163
163
  - app/assets/config/effective_search_manifest.js
164
164
  - app/assets/javascripts/effective_search.js
165
+ - app/assets/javascripts/effective_search/admin.js
165
166
  - app/assets/javascripts/effective_search/base.js
166
167
  - app/assets/stylesheets/effective_search.scss
168
+ - app/assets/stylesheets/effective_search/admin.scss
167
169
  - app/assets/stylesheets/effective_search/base.scss
168
170
  - app/controllers/admin/search_contents_controller.rb
171
+ - app/controllers/admin/search_controller.rb
169
172
  - app/controllers/effective/search_controller.rb
170
173
  - app/datatables/admin/effective_search_contents_datatable.rb
171
174
  - app/helpers/effective_search_helper.rb
175
+ - app/models/concerns/effective_search_admin_search.rb
172
176
  - app/models/concerns/effective_search_search.rb
177
+ - app/models/effective/admin_search.rb
173
178
  - app/models/effective/search.rb
174
179
  - app/models/effective/search_content.rb
180
+ - app/views/admin/search/_applicant.html.haml
181
+ - app/views/admin/search/_form_nav_icon.html.haml
182
+ - app/views/admin/search/_layout.html.haml
183
+ - app/views/admin/search/_modal.html.haml
184
+ - app/views/admin/search/_order.html.haml
185
+ - app/views/admin/search/_organization.html.haml
186
+ - app/views/admin/search/_search.html.haml
187
+ - app/views/admin/search/_user.html.haml
188
+ - app/views/admin/search/index.html.haml
175
189
  - app/views/admin/search_contents/_form.html.haml
176
190
  - app/views/admin/search_contents/_form_search_content.html.haml
177
191
  - app/views/effective/search/_form.html.haml