diff --git a/app/controllers/api/submitters_autocomplete_controller.rb b/app/controllers/api/submitters_autocomplete_controller.rb new file mode 100644 index 00000000..ff7dc2e6 --- /dev/null +++ b/app/controllers/api/submitters_autocomplete_controller.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Api + class SubmittersAutocompleteController < ApiBaseController + load_and_authorize_resource :submitter, parent: false + + SELECT_COLUMNS = %w[email phone name].freeze + LIMIT = 100 + + def index + submitters = search_submitters(@submitters) + + values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', ')) + + attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h } + attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present? + + render json: attrs + end + + private + + def search_submitters(submitters) + if SELECT_COLUMNS.include?(params[:field]) + column = Submitter.arel_table[params[:field].to_sym] + + term = "%#{params[:q].downcase}%" + + submitters.where(column.lower.matches(term)) + else + Submitters.search(submitters, params[:q]) + end + end + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 8860eb7f..f9d1e043 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -15,6 +15,7 @@ import DownloadButton from './elements/download_button' import SetOriginUrl from './elements/set_origin_url' import SetTimezone from './elements/set_timezone' import AutoresizeTextarea from './elements/autoresize_textarea' +import SubmittersAutocomplete from './elements/submitter_autocomplete' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -43,6 +44,7 @@ window.customElements.define('download-button', DownloadButton) window.customElements.define('set-origin-url', SetOriginUrl) window.customElements.define('set-timezone', SetTimezone) window.customElements.define('autoresize-textarea', AutoresizeTextarea) +window.customElements.define('submitters-autocomplete', SubmittersAutocomplete) document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody) document.addEventListener('turbo:submit-end', async (event) => { diff --git a/app/javascript/application.scss b/app/javascript/application.scss index e731dfc5..3adfab12 100644 --- a/app/javascript/application.scss +++ b/app/javascript/application.scss @@ -91,3 +91,30 @@ button[disabled] .enabled { right: auto; bottom: auto; } + +.autocomplete { + background: white; + z-index: 1000; + font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + overflow: auto; + box-sizing: border-box; + @apply border border-base-300; +} + +.autocomplete * { + font: inherit; +} + +.autocomplete > div { + padding: 0 4px; +} + +.autocomplete .group { + background: #eee; +} + +.autocomplete > div:hover:not(.group), +.autocomplete > div.selected { + @apply bg-base-300; + cursor: pointer; +} diff --git a/app/javascript/elements/submitter_autocomplete.js b/app/javascript/elements/submitter_autocomplete.js new file mode 100644 index 00000000..d6fcf707 --- /dev/null +++ b/app/javascript/elements/submitter_autocomplete.js @@ -0,0 +1,56 @@ +import autocomplete from 'autocompleter' + +export default class extends HTMLElement { + connectedCallback () { + autocomplete({ + input: this.input, + preventSubmit: 1, + minLength: 1, + showOnFocus: true, + onSelect: this.onSelect, + render: this.render, + fetch: this.fetch + }) + } + + onSelect = (item) => { + const fields = ['email', 'name', 'phone'] + const submitterItemEl = this.closest('submitter-item') + + fields.forEach((field) => { + const input = submitterItemEl.querySelector(`submitters-autocomplete[data-field="${field}"] input`) + + if (input && item[field]) { + input.value = item[field] + } + }) + } + + fetch = (text, resolve) => { + if (text) { + const queryParams = new URLSearchParams({ q: text, field: this.dataset.field }) + + fetch('/api/submitters_autocomplete?' + queryParams).then(async (resp) => { + const items = await resp.json() + + resolve(items) + }).catch(() => { + resolve([]) + }) + } else { + resolve([]) + } + } + + render = (item) => { + const div = document.createElement('div') + + div.textContent = item[this.dataset.field] + + return div + } + + get input () { + return this.querySelector('input') + } +} diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 1b54ac0a..ae2a59cd 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -10,19 +10,25 @@