add submitter fields autocomplete
This commit is contained in:
@@ -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
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,25 @@
|
||||
</div>
|
||||
<div class="grid <%= 'md:grid-cols-2' if template.submitters.size > 1 %> gap-4">
|
||||
<% template.submitters.each do |item| %>
|
||||
<div class="form-control">
|
||||
<submitter-item class="form-control">
|
||||
<% if template.submitters.size > 1 %>
|
||||
<label class="label pt-0 pb-1 text-xs">
|
||||
<span class="label-text"> <%= item['name'] %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered" placeholder="Name" required>
|
||||
<submitters-autocomplete data-field="name">
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Name" required>
|
||||
</submitters-autocomplete>
|
||||
<div class="grid <%= 'md:grid-cols-2 gap-1' if template.submitters.size == 1 %>">
|
||||
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Email (optional)">
|
||||
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Phone (optional)">
|
||||
<submitters-autocomplete data-field="email">
|
||||
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered mt-1.5 w-full" placeholder="Email (optional)">
|
||||
</submitters-autocomplete>
|
||||
<submitters-autocomplete data-field="phone">
|
||||
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered mt-1.5 w-full" placeholder="Phone (optional)">
|
||||
</submitters-autocomplete>
|
||||
</div>
|
||||
</div>
|
||||
</submitter-item>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<% template.submitters.each do |item| %>
|
||||
<div class="form-control">
|
||||
<submitter-item class="form-control">
|
||||
<label class="label pt-0 pb-1 text-xs">
|
||||
<span class="label-text"> <%= item['name'] %></span>
|
||||
</label>
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered" placeholder="Email" required>
|
||||
</div>
|
||||
<submitters-autocomplete data-field="email">
|
||||
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Email" required>
|
||||
</submitters-autocomplete>
|
||||
</submitter-item>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,25 +8,33 @@
|
||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 <%= template.submitters.size > 1 ? 'gap-4' : 'gap-1' %>">
|
||||
<div class="grid <%= template.submitters.size > 1 ? 'md:grid-cols-2 gap-4' : 'gap-1' %>">
|
||||
<% template.submitters.each do |item| %>
|
||||
<div class="form-control">
|
||||
<% if template.submitters.size > 1 %>
|
||||
<label class="label pt-0 pb-1 text-xs">
|
||||
<span class="label-text"> <%= item['name'] %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered" placeholder="Phone" required>
|
||||
<% if template.submitters.size > 1 %>
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Name (optional)">
|
||||
<% end %>
|
||||
</div>
|
||||
<% if template.submitters.size == 1 %>
|
||||
<div class="form-control flex">
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered" placeholder="Name (optional)">
|
||||
<submitter-item class="grid <%= template.submitters.size > 1 ? 'gap-4' : 'md:grid-cols-2 gap-1' %>">
|
||||
<div class="form-control">
|
||||
<% if template.submitters.size > 1 %>
|
||||
<label class="label pt-0 pb-1 text-xs">
|
||||
<span class="label-text"> <%= item['name'] %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="phone">
|
||||
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Phone" required>
|
||||
</submitters-autocomplete>
|
||||
<% if template.submitters.size > 1 %>
|
||||
<submitters-autocomplete data-field="name">
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered mt-1.5 w-full" placeholder="Name (optional)">
|
||||
</submitters-autocomplete>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if template.submitters.size == 1 %>
|
||||
<div class="form-control flex">
|
||||
<submitters-autocomplete data-field="name">
|
||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Name (optional)">
|
||||
</submitters-autocomplete>
|
||||
</div>
|
||||
<% end %>
|
||||
</submitter-item>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@ Rails.application.routes.draw do
|
||||
|
||||
namespace :api, defaults: { format: :json } do
|
||||
resources :attachments, only: %i[create]
|
||||
resources :submitters_autocomplete, only: %i[index]
|
||||
resources :submitter_email_clicks, only: %i[create]
|
||||
resources :submitter_form_views, only: %i[create]
|
||||
resources :submissions, only: %i[create]
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
module Submitters
|
||||
module_function
|
||||
|
||||
def search(submitters, keyword)
|
||||
return submitters if keyword.blank?
|
||||
|
||||
term = "%#{keyword.downcase}%"
|
||||
|
||||
arel_table = Submitter.arel_table
|
||||
|
||||
arel = arel_table[:email].lower.matches(term)
|
||||
.or(arel_table[:phone].matches(term))
|
||||
.or(arel_table[:name].lower.matches(term))
|
||||
|
||||
submitters.where(arel)
|
||||
end
|
||||
|
||||
def select_attachments_for_download(submitter)
|
||||
original_documents = submitter.submission.template.documents.preload(:blob)
|
||||
is_more_than_two_images = original_documents.count(&:image?) > 1
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@hotwired/turbo-rails": "^7.3.0",
|
||||
"@rails/activestorage": "^7.0.0",
|
||||
"@tabler/icons-vue": "^2.20.0",
|
||||
"autocompleter": "^9.1.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
|
||||
@@ -1737,6 +1737,11 @@ array.prototype.flatmap@^1.3.1:
|
||||
es-abstract "^1.20.4"
|
||||
es-shim-unscopables "^1.0.0"
|
||||
|
||||
autocompleter@^9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/autocompleter/-/autocompleter-9.1.0.tgz#c7248a8cc0c58376d0969734c40e29626d950f04"
|
||||
integrity sha512-dwAYJTaLHj1MpzCZXFg8WLmk+tgQ85OEDFfBegGnA+uVUZyzW/PZAdjSXR3fOt0+q8ZeEfMDiHDqw60uoF1NDg==
|
||||
|
||||
autoprefixer@^10.4.14:
|
||||
version "10.4.14"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
|
||||
|
||||
Reference in New Issue
Block a user