Skip to content

Commit

Permalink
Merge pull request #228 from lnu-norge/search-by-name
Browse files Browse the repository at this point in the history
Search by space title
  • Loading branch information
DanielJackson-Oslo authored Feb 21, 2024
2 parents 28b4085 + a6c9b03 commit aa3d368
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 38 deletions.
17 changes: 12 additions & 5 deletions app/controllers/spaces_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ def get_address_params(params)

def filter_spaces(params)
space_types = params[:space_types]&.map(&:to_i)
facilities = params[:facilities]&.map(&:to_i)

spaces = Space.includes([:images]).filter_on_location(
params[:north_west_lat],
Expand All @@ -180,10 +179,18 @@ def filter_spaces(params)
params[:south_east_lng]
)

spaces = spaces.filter_on_space_types(space_types) unless space_types.nil?
spaces = spaces.order("star_rating DESC NULLS LAST")
spaces = Space.filter_on_facilities(spaces, facilities) unless facilities.nil?
spaces
spaces = spaces.filter_on_title(params[:search_for_title]) if params[:search_for_title].present?
spaces = spaces.filter_on_space_types(space_types) if space_types.present?

filter_on_facilities_and_return_ordered_spaces(spaces:, params:)
end

def filter_on_facilities_and_return_ordered_spaces(spaces:, params:)
facilities = params[:facilities]&.map(&:to_i)

return spaces.order("star_rating DESC NULLS LAST") if facilities.blank?

Space.filter_on_facilities(spaces, params[:facilities])
end

def space_params # rubocop:disable Metrics/MethodLength
Expand Down
16 changes: 14 additions & 2 deletions app/javascript/controllers/duplicate_checker_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,27 @@ export default class extends Controller {
form.addEventListener('change', () => {
this.checkDuplicates()
});

const titleField = form.querySelector("#space_title")
titleField.addEventListener('input', (e) => {
if (e.target.value.length < 5) {
return
}

this.checkDuplicates()
})
}

async checkDuplicates() {
const data = new FormData(this.element);
const title = data.get("space[title]");
const address = data.get("space[address]");
const post_number = data.get("space[post_number]");

if (!post_number || (!address && !post_number)) return
const title = data.get("space[title]");
const enoughDataToFindByTitle = title.length > 5
const enoughDataToFindByAddress = (!post_number || (!address && !post_number))

if (!enoughDataToFindByTitle || !enoughDataToFindByAddress) return

if (this.checkIfDataIsStale(title, address, post_number)) {
return
Expand Down
98 changes: 77 additions & 21 deletions app/javascript/controllers/mapbox_controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import mapboxgl from 'mapbox-gl';
import { Controller } from "@hotwired/stimulus";
import capsule_html from './search_and_filter/capsule_html';

export default class extends Controller {
static targets = [ "facility", "spaceType", "location", "searchBox", "form", "filterCapsules", "searchArea" ]
static targets = [
"facility",
"spaceType",
"location",
"searchBox",
"title",
"form",
"filterCapsules",
"searchArea"
]

async initialize() {
mapboxgl.accessToken = this.element.dataset.apiKey;
Expand All @@ -16,16 +26,14 @@ export default class extends Controller {
};
}

toggleSearchBox() {
this.searchBoxTarget.classList.toggle("hidden");
showSearchBox() {
this.searchBoxTarget.classList.remove("hidden");
let searchField = document.getElementById("locationInput-ts-control");
searchField.focus();
}

closeModal(e) {
if (e.key === "Enter") {
this.toggleSearchBox();
}
hideSearchBox() {
this.searchBoxTarget.classList.add("hidden");
}

requestPosition() {
Expand Down Expand Up @@ -60,36 +68,41 @@ export default class extends Controller {
}

updateFilterCapsules() {
const capsuleHtml = (title) => `<button
data-action="click->mapbox#disableCapsule"
class="bg-white px-2.5 py-0.5 mt-2 rounded-full border border-gray-200 hover:border-lnu-pink whitespace-nowrap">
${title}
</button>`
const titleCapsule = this.titleTarget.value ? capsule_html(`"${this.titleTarget.value}"`) : '';

const facilityCapsules = this.facilityTargets.map(t =>
t.checked ? capsuleHtml(t.id) : ''
t.checked ? capsule_html(t.id) : ''
).join('');

const spaceTypeCapsules = this.spaceTypeTargets.map(t =>
t.checked ? capsuleHtml(t.id) : ''
t.checked ? capsule_html(t.id) : ''
).join('');

this.filterCapsulesTarget.innerHTML = facilityCapsules + spaceTypeCapsules;
this.filterCapsulesTarget.innerHTML = titleCapsule + facilityCapsules + spaceTypeCapsules;
}

disableCapsule(event) {
let foundFilterToReset = false;

this.facilityTargets.forEach(t => {
if (t.id === event.target.innerText) {
t.checked = false;
foundFilterToReset = true;
}
});

this.spaceTypeTargets.forEach(t => {
if (t.id === event.target.innerText) {
t.checked = false;
foundFilterToReset = true;
}
});

if (!foundFilterToReset) {
// Then it's the title serach capsule
this.titleTarget.value = '';
}

this.updateFilterCapsules();
this.loadNewMapPosition();
this.updateUrl();
Expand All @@ -101,10 +114,12 @@ export default class extends Controller {
const selectedFacilities = this.selectedFacilities();
const selectedSpaceTypes = this.selectedSpaceTypes();
const selectedLocation = this.selectedLocation();
const searchForTitle = this.titleTarget.value;

this.setOrDeleteToUrl('selectedFacilities', selectedFacilities);
this.setOrDeleteToUrl('selectedSpaceTypes', selectedSpaceTypes);
this.setOrDeleteToUrl('selectedLocation', selectedLocation);
this.setOrDeleteToUrl('searchForTitle', searchForTitle);
}

setOrDeleteToUrl(key, value) {
Expand Down Expand Up @@ -137,9 +152,11 @@ export default class extends Controller {
const selectedFacilities = url.searchParams.get('selectedFacilities');
const selectedSpaceTypes = url.searchParams.get('selectedSpaceTypes');
const selectedLocation = url.searchParams.get('selectedLocation');
const searchForTitle = url.searchParams.get('searchForTitle');

this.parseSelectedFacilities(selectedFacilities);
this.parseSelectedSpaceTypes(selectedSpaceTypes);
this.parseSearchForTitle(searchForTitle);

this.updateFilterCapsules();

Expand All @@ -157,6 +174,13 @@ export default class extends Controller {
}
}

parseSearchForTitle(searchForTitle) {
if(searchForTitle) {
this.titleTarget.value = searchForTitle;
} else {
this.titleTarget.value = '';
}
}
parseSelectedFacilities(selectedFacilities) {
if(!selectedFacilities) return;

Expand Down Expand Up @@ -204,19 +228,39 @@ export default class extends Controller {
}
});

this.titleTarget.oninput = () => {
this.debounce(
"titleSearch",
500,
() => this.runSearch()
);
}

this.debounce = (name, time, callback) => {
this.debounceTimeouts = this.debounceTimeouts || {};

if (this.debounceTimeouts[name]) {
clearTimeout(this.debounceTimeouts[name]);
}
this.debounceTimeouts[name] = setTimeout(callback, time);
}

this.clearDebounce = (name) => {
if (this.debounceTimeouts &&
this.debounceTimeouts[name]) {
clearTimeout(this.debounceTimeouts[name]);
}
}

this.spaceTypeTargets.forEach(spaceType => {
spaceType.onchange = () => {
this.updateFilterCapsules();
this.loadNewMapPosition();
this.updateUrl();
this.runSearch()
};
});

this.facilityTargets.forEach(spaceType => {
spaceType.onchange = () => {
this.updateFilterCapsules();
this.loadNewMapPosition();
this.updateUrl();
this.runSearch()
};
});

Expand All @@ -227,9 +271,18 @@ export default class extends Controller {
this.formTarget.onsubmit = (event) => {
// To stop the form from submitting, as that currently does nothing but refresh the page.
event.preventDefault()
this.clearDebounce("titleSearch")
this.submitSearch(event)
this.hideSearchBox()
};
}

runSearch() {
this.updateFilterCapsules();
this.loadNewMapPosition();
this.updateUrl();
}

loadPositionOn(event) {
this.map.on(event, () => {
this.updateUrl();
Expand Down Expand Up @@ -287,12 +340,15 @@ export default class extends Controller {
t.checked ? `space_types[]=${encodeURIComponent(t.name)}&` : ''
).join('');

const title = this.titleTarget.value;

return [
'/spaces_search?',
`north_west_lat=${northWest.lat}&`,
`north_west_lng=${northWest.lng}&`,
`south_east_lat=${southEast.lat}&`,
`south_east_lng=${southEast.lng}&`,
`search_for_title=${title}&`,
facilitiesString,
spaceTypesString,
].join('');
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/controllers/search_and_filter/capsule_html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const capsule_html = (title) => `<button
data-action="click->mapbox#disableCapsule"
class="bg-white px-2.5 py-0.5 mt-2 rounded-full border border-gray-200 hover:border-lnu-pink whitespace-nowrap">
${title}
</button>`

export default capsule_html
1 change: 1 addition & 0 deletions app/models/space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Space < ApplicationRecord # rubocop:disable Metrics/ClassLength
accepts_nested_attributes_for :facility_reviews
accepts_nested_attributes_for :space_facilities

scope :filter_on_title, ->(title) { where("title ILIKE ?", "%#{title}%") }
scope :filter_on_space_types, ->(space_type_ids) { joins(:space_types).where(space_types: space_type_ids).distinct }
scope :filter_on_location, lambda { |north_west_lat, north_west_lng, south_east_lat, south_east_lng|
where(":north_west_lat >= lat AND :north_west_lng <= lng AND :south_east_lat <= lat AND :south_east_lng >= lng",
Expand Down
21 changes: 17 additions & 4 deletions app/services/spaces/duplicate_detector_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ def call
def find_potential_duplicates
duplicates = []

# If any full matches, just return those first:
duplicates << full_match
return duplicates.flatten if duplicates[0].present?

# If any matches by full address, return those without checking further:
duplicates << by_full_address
return duplicates.flatten.compact if duplicates[0].present?

# Then return any matches by title and/or post
duplicates << by_post_and_title
duplicates << by_title_or_post_and_title
duplicates.flatten.compact.uniq
end

Expand All @@ -47,6 +44,22 @@ def full_match
})
end

def by_title_or_post_and_title
if post_number.present?
by_post_and_title
else
by_title_only
end
end

def by_title_only
return nil if title.blank?

Space.where(
"title ILIKE ?", "%#{title}%"
)
end

def by_post_and_title
return nil unless title.present? && post_number.present?

Expand Down
2 changes: 1 addition & 1 deletion app/views/spaces/index/_filter_button.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<nav class="fixed md:hidden m-4 md:m-0 z-10 top-0 md:top-14 right-0 left-0 justify-center md:block md:px-4 md:pt-4 md:mb-8">
<button
id="toggle_search_box"
data-action="click->mapbox#toggleSearchBox"
data-action="click->mapbox#showSearchBox"
class="border-white hover:border-lnu-pink w-full md:w-full border-2 py-3 px-4 bg-white rounded-md shadow-xl ">
<span class="flex justify-between">
<span class="inline-flex items-center justify-start">
Expand Down
4 changes: 2 additions & 2 deletions app/views/spaces/index/_filters.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="flex flex-col">
<header class="modal-header md:hidden flex justify-between w-full">
<%= button_tag data: {
action: "click->mapbox#toggleSearchBox"
action: "click->mapbox#hideSearchBox"
}, class: "md:hidden hover:underline p-4 -ml-4 inline-flex content-center items-center" do %>
<%= inline_svg_tag 'x', class: 'inline-block' %>
<%= t("close") %>
Expand All @@ -18,7 +18,7 @@

<footer class="sticky md:sr-only bottom-0 left-0 right-0 modal-footer">
<%= button_tag data: {
action: "click->mapbox#toggleSearchBox"
action: "click->mapbox#hideSearchBox"
}, class: "modal-button" do %>
<%= inline_svg_tag 'search', class: 'inline-block' %>
<%= t('space_filter.show_spaces', count: '') %>
Expand Down
17 changes: 14 additions & 3 deletions app/views/spaces/index/_search_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<%= form_with data: { mapbox_target: 'form', action: "keydown->mapbox#closeModal" } do |f| %>
<%= form_with data: {
mapbox_target: 'form'
} do |f| %>

<div class="mb-8">
<%= f.label t("space_filter.title"), class: "text-xl font-bold" %>
<div class="mt-2">
<%= f.text_field :title,
data: { mapbox_target: "title" },
class: "text_field shadow" %>
</div>
</div>

<div class="mb-8">
<%= f.label t("space_filter.location"), class: "text-xl font-bold" %>
<div class="mt-2">
<div data-mapbox-target="location">
<select id="locationInput"
class="w-full rounded shadow border border-gray-300 focus:ring-lnu-pink focus:border-lnu-pink"
placeholder=<%= t("space_filter.location") %>
class="w-full rounded shadow focus:ring-lnu-pink focus:border-lnu-pink"
data-controller="locationsearch">
</select>
</div>
Expand Down
1 change: 1 addition & 0 deletions config/locales/nb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ nb:
other: "%{count} lokaler passer:"
edit_filter: 'Rediger filtre'
location: 'Sted'
title: 'Navn på lokalet'
more_filters: 'Flere filtre'
map_reload: 'Søk i området'
admin:
Expand Down

0 comments on commit aa3d368

Please sign in to comment.