21 changed files with 331 additions and 40 deletions
@ -0,0 +1,33 @@ |
|||||||
|
class AddressOptionsController < ApplicationController |
||||||
|
def index |
||||||
|
query = params[:query] |
||||||
|
service = AddressClient.new(address: query) |
||||||
|
service.call |
||||||
|
|
||||||
|
if service.error.present? |
||||||
|
render json: { error: service.error }, status: :unprocessable_entity |
||||||
|
else |
||||||
|
render json: service.result.map { |result| { address: result["ADDRESS"], uprn: result["UPRN"] } } |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def current |
||||||
|
log_id = params[:log_id] |
||||||
|
sales_log = SalesLog.find_by(id: log_id) |
||||||
|
uprn = sales_log&.address_search |
||||||
|
|
||||||
|
if uprn.present? |
||||||
|
service = AddressClient.new(uprn: uprn) |
||||||
|
service.call |
||||||
|
|
||||||
|
if service.error.present? |
||||||
|
render json: { error: service.error }, status: :unprocessable_entity |
||||||
|
else |
||||||
|
address = service.result.find { |result| result["UPRN"] == uprn }&.dig("ADDRESS") |
||||||
|
render json: { stored_value: { uprn:, address: } } |
||||||
|
end |
||||||
|
else |
||||||
|
render json: { stored_value: nil } |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,115 @@ |
|||||||
|
import {Controller} from '@hotwired/stimulus' |
||||||
|
import accessibleAutocomplete from 'accessible-autocomplete' |
||||||
|
import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' |
||||||
|
import {searchableName} from "../modules/search"; |
||||||
|
|
||||||
|
const options = [] |
||||||
|
|
||||||
|
const fetchOptions = async (query) => { |
||||||
|
const response = await fetch(`/address_options?query=${query}`) |
||||||
|
const data = await response.json() |
||||||
|
console.log(data) |
||||||
|
return data |
||||||
|
} |
||||||
|
|
||||||
|
const fetchAndPopulateSearchResults = async (query, populateResults, populateOptions, selectEl) => { |
||||||
|
if (/\S/.test(query)) { |
||||||
|
const results = await fetchOptions(query) |
||||||
|
console.log(results) // address and uprn keys returned per result
|
||||||
|
populateOptions(results, selectEl) |
||||||
|
populateResults(Object.values(results).map((o) => o.address)) |
||||||
|
// populateResults(results)
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const populateOptions = (results, selectEl) => { |
||||||
|
selectEl.innerHTML = '' |
||||||
|
|
||||||
|
results.forEach((result) => { |
||||||
|
const option = document.createElement('option') |
||||||
|
option.value = result.uprn |
||||||
|
option.innerHTML = result.address |
||||||
|
option.setAttribute('address', result.address) |
||||||
|
selectEl.appendChild(option) |
||||||
|
options.push(option) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// const populateOptions = (results, selectEl) => {
|
||||||
|
// selectEl.innerHTML = ''
|
||||||
|
//
|
||||||
|
// Object.keys(results).forEach((key) => {
|
||||||
|
// const option = document.createElement('option')
|
||||||
|
// option.value = key
|
||||||
|
// option.innerHTML = results[key].value
|
||||||
|
// if (results[key].hint) { option.setAttribute('data-hint', results[key].hint) }
|
||||||
|
// option.setAttribute('text', searchableName(results[key]))
|
||||||
|
// selectEl.appendChild(option)
|
||||||
|
// options.push(option)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default class extends Controller { |
||||||
|
connect () { |
||||||
|
const selectEl = this.element |
||||||
|
|
||||||
|
const currentValue = this.getCurrentValue() |
||||||
|
console.log(selectEl) |
||||||
|
|
||||||
|
if (currentValue && currentValue.stored_value) { |
||||||
|
console.log(currentValue) |
||||||
|
const option = document.createElement('option') |
||||||
|
option.value = currentValue.stored_value.uprn |
||||||
|
option.innerHTML = currentValue.stored_value.address |
||||||
|
option.selected = true |
||||||
|
selectEl.appendChild(option) |
||||||
|
} |
||||||
|
|
||||||
|
accessibleAutocomplete.enhanceSelectElement({ |
||||||
|
defaultValue: '', |
||||||
|
selectElement: selectEl, |
||||||
|
minLength: 1, |
||||||
|
source: (query, populateResults) => { |
||||||
|
fetchAndPopulateSearchResults(query, populateResults, populateOptions, selectEl) |
||||||
|
}, |
||||||
|
autoselect: true, |
||||||
|
showNoOptionsFound: true, |
||||||
|
placeholder: currentValue?.stored_value?.address || 'Start typing to search', |
||||||
|
templates: { suggestion: (value) => value }, |
||||||
|
onConfirm: (val) => { |
||||||
|
const selectedResult = Array.from(selectEl.options).find(option => option.address === val) |
||||||
|
|
||||||
|
if (selectedResult) { |
||||||
|
selectedResult.selected = true |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
fetchOptions(query, populateResults) { |
||||||
|
fetch(`/address_options?query=${query}`) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(data => { |
||||||
|
console.log(data) |
||||||
|
const results = data.map(result => result.uprn) |
||||||
|
populateResults(results.slice(0, 10)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async getCurrentValue() { |
||||||
|
const currentPageUrl = window.location.href; |
||||||
|
console.log(currentPageUrl); |
||||||
|
const match = currentPageUrl.match(/sales-logs\/(\d+)\/address-search/); |
||||||
|
const id = match ? match[1] : null; |
||||||
|
|
||||||
|
if (id) { |
||||||
|
const response = await fetch(`/address_options/current?log_id=${id}`); |
||||||
|
const data = await response.json(); |
||||||
|
console.log(data); |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
module AddressOptionsHelper |
||||||
|
end |
@ -0,0 +1,24 @@ |
|||||||
|
class Form::Sales::Pages::AddressSearch < ::Form::Page |
||||||
|
def initialize(id, hsh, subsection) |
||||||
|
super |
||||||
|
@id = "address_search" |
||||||
|
@copy_key = "sales.property_information.address_search" |
||||||
|
@depends_on = [ |
||||||
|
{ "uprn_known" => nil }, |
||||||
|
{ "uprn_known" => 0 }, |
||||||
|
{ "uprn_confirmed" => 0 }, |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def questions |
||||||
|
@questions ||= [ |
||||||
|
Form::Sales::Questions::AddressSearch.new(nil, nil, self), |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def skip_href(log = nil) |
||||||
|
return unless log |
||||||
|
|
||||||
|
"/#{log.model_name.param_key.dasherize}s/#{log.id}/property-number-of-bedrooms" |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,32 @@ |
|||||||
|
class Form::Sales::Questions::AddressSearch < ::Form::Question |
||||||
|
def initialize(id, hsh, page) |
||||||
|
super |
||||||
|
@id = "address_search" |
||||||
|
@copy_key = "sales.property_information.address_search" |
||||||
|
@error_label = "Enter search query" |
||||||
|
@type = "address_autocomplete" |
||||||
|
@plain_label = true |
||||||
|
end |
||||||
|
|
||||||
|
def answer_options(log = nil, _user = nil) |
||||||
|
return {} unless ActiveRecord::Base.connected? |
||||||
|
return {} unless log&.address_options |
||||||
|
|
||||||
|
answer_opts = {} |
||||||
|
|
||||||
|
(0...[log.address_options.count, 10].min).each do |i| |
||||||
|
answer_opts[log.address_options[i][:uprn]] = { "value" => log.address_options[i][:address] } |
||||||
|
end |
||||||
|
|
||||||
|
answer_opts["divider"] = { "value" => true } |
||||||
|
answer_opts |
||||||
|
end |
||||||
|
|
||||||
|
def displayed_answer_options(log, user = nil) |
||||||
|
answer_options(log, user).transform_values { |value| value["value"] } || {} |
||||||
|
end |
||||||
|
|
||||||
|
def hidden_in_check_answers?(log, _current_user = nil) |
||||||
|
(log.uprn_known == 1 || log.uprn_confirmed == 1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,23 @@ |
|||||||
|
<% selected = @log.public_send(question.id) || "" %> |
||||||
|
<% answers = question.displayed_answer_options(@log, current_user).map { |key, value| OpenStruct.new(id: key, name: select_option_name(value), resource: value) } %> |
||||||
|
<%= render partial: "form/guidance/#{question.top_guidance_partial}" if question.top_guidance? %> |
||||||
|
|
||||||
|
<%= f.govuk_select(question.id.to_sym, |
||||||
|
label: legend(question, page_header, conditional), |
||||||
|
"data-controller": "address-autocomplete", |
||||||
|
caption: caption(caption_text, page_header, conditional), |
||||||
|
hint: { text: question.hint_text&.html_safe }) do %> |
||||||
|
<% if answers.any? %> |
||||||
|
<% answers.each do |answer| %> |
||||||
|
<option value="<%= answer.id %>" |
||||||
|
data-synonyms="<%= answer_option_synonyms(answer.resource) %>" |
||||||
|
data-append="<%= answer_option_append(answer.resource) %>" |
||||||
|
data-hint="<%= answer_option_hint(answer.resource) %>" |
||||||
|
<%= question.answer_selected?(@log, answer) ? "selected" : "" %>><%= answer.name || answer.resource %></option> |
||||||
|
<% end %> |
||||||
|
<% else %> |
||||||
|
<option value="" disabled></option> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "form/guidance/#{question.bottom_guidance_partial}" if question.bottom_guidance? %> |
@ -0,0 +1,5 @@ |
|||||||
|
class AddAddressSearchToSalesLogs < ActiveRecord::Migration[7.0] |
||||||
|
def change |
||||||
|
add_column :sales_logs, :address_search, :string |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue