Skip to content
Snippets Groups Projects
Commit 4ce5505a authored by Kerdo Kurs's avatar Kerdo Kurs
Browse files

implement searching #23

parent 6812d43f
No related branches found
No related tags found
1 merge request!31Implement searching, storing and revisiting searches
defmodule PropTrackr.Search do
use Ecto.Schema
import Ecto.Changeset
alias PropTrackr.Repo
alias PropTrackr.Properties.Property
alias PropTrackr.NotInterested
import Ecto.Query
schema "searches" do
field :type, Ecto.Enum, values: [:rent, :sell, :any]
field :min_price, :float
field :max_price, :float
field :min_rooms, :integer
field :max_rooms, :integer
field :location, :string
field :areas, {:array, :string}
belongs_to :user, PropTrackr.Accounts.User
end
def create_query(struct, current_user) do
areas = struct[:areas]
locations = Enum.map(areas, fn area -> area <> ", " <> struct[:location] end)
current_user_id = if current_user != nil do
current_user.id
else
nil
end
query = from(
p in Property,
left_join: ni in NotInterested,
on: ni.property_id == p.id and ni.user_id == ^(current_user_id || 0),
order_by: [asc: not is_nil(ni.id), asc: p.state, asc: p.inserted_at],
where: p.location in ^locations,
where: p.price >= ^struct.min_price,
where: p.price <= ^struct.max_price,
where: p.room_count >= ^struct.min_rooms,
where: p.room_count <= ^struct.max_rooms,
where: p.state != :unavailable,
preload: [:photos],
select: p,
)
query = if struct.type == :any do
query
else
query |> where(type: ^struct.type)
end
query
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:type, :location, :min_price, :max_price, :min_rooms, :max_rooms, :areas])
|> validate_required([:type, :location, :min_price, :max_price, :min_rooms, :max_rooms])
|> validate_minmax(:min_price, :max_price)
|> validate_minmax(:min_rooms, :max_rooms)
end
defp validate_minmax(changeset, min_key, max_key) do
min = get_field(changeset, min_key)
max = get_field(changeset, max_key)
cond do
min < 0 -> add_error(changeset, min_key, "cannot be negative")
min > max -> add_error(changeset, min_key, "cannot be greater than #{max_key}")
true -> changeset
end
end
end
...@@ -42,7 +42,8 @@ defmodule PropTrackrWeb.PropertiesController do ...@@ -42,7 +42,8 @@ defmodule PropTrackrWeb.PropertiesController do
render(conn, "index.html", render(conn, "index.html",
properties: properties, properties: properties,
favorites: favorites, favorites: favorites,
not_interested: not_interested not_interested: not_interested,
is_search: false,
) )
end end
......
<.header> <.header>
Welcome to PropTrackr! <%= if header_text = @conn.assigns[:header_text] do %>
<%= header_text %>
<% else %>
Welcome to PropTrackr!
<% end %>
</.header> </.header>
<%= if @conn.assigns[:current_user] do %> <%= if not @is_search and @conn.assigns[:current_user] do %>
<div class="flex flex-row justify-end gap-x-2"> <div class="flex flex-row justify-end gap-x-2">
<.link href={~p"/properties/new"}> <.link href={~p"/properties/new"}>
<.button type="button" class="text-white rounded px-4 py-2" id="add_advertisement"> <.button type="button" class="text-white rounded px-4 py-2" id="add_advertisement">
...@@ -15,6 +19,7 @@ ...@@ -15,6 +19,7 @@
<%= if @properties == [] do %> <%= if @properties == [] do %>
<p>No advertisements at the moment</p> <p>No advertisements at the moment</p>
<% end %> <% end %>
<div id="properties" class="flex flex-col gap-y-4 mt-20"> <div id="properties" class="flex flex-col gap-y-4 mt-20">
<%= for property <- @properties do %> <%= for property <- @properties do %>
<div class="bg-white border-black border rounded px-4 py-2"> <div class="bg-white border-black border rounded px-4 py-2">
...@@ -46,6 +51,7 @@ ...@@ -46,6 +51,7 @@
> >
<%= if property.id in @favorites, do: "★", else: "☆" %> <%= if property.id in @favorites, do: "★", else: "☆" %>
</button> </button>
<%= if not @is_search do %>
<div class="relative"> <div class="relative">
<button type="button" class="interest-menu-button" data-property-reference={property.reference}> <button type="button" class="interest-menu-button" data-property-reference={property.reference}>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
...@@ -65,6 +71,7 @@ ...@@ -65,6 +71,7 @@
</div> </div>
</div> </div>
</div> </div>
<% end %>
</div> </div>
<% end %> <% end %>
</div> </div>
......
defmodule PropTrackrWeb.SearchController do
use PropTrackrWeb, :controller
import Ecto.Query, only: [from: 2]
alias PropTrackr.Repo
alias PropTrackr.Search
alias PropTrackr.Properties.Property
alias PropTrackr.Favorites.Favorite
alias PropTrackr.NotInterested
def index(conn, _params) do
min_price = from(p in Property, select: min(p.price)) |> Repo.one
max_price = from(p in Property, select: max(p.price)) |> Repo.one
min_rooms = 0
max_rooms = from(p in Property, select: max(p.room_count)) |> Repo.one
locations = from(p in Property, select: p.location) |> Repo.all
cities = Enum.map(locations, fn location -> String.split(location, ",") |> hd |> String.trim() end)
cities = Enum.uniq(cities)
countries = Enum.map(locations, fn location -> String.split(location, ",") |> tl |> hd |> String.trim() end)
countries = Enum.uniq(countries)
changeset = Search.changeset(%Search{})
render(
conn, "search.html",
changeset: changeset,
min_price: min_price,
max_price: max_price,
min_rooms: min_rooms,
max_rooms: max_rooms,
cities: cities,
countries: countries,
)
end
def create(conn, %{ "search" => search }) do
current_user = conn.assigns.current_user
changeset = Search.changeset(%Search{}, search)
query = Search.create_query(changeset.changes, current_user)
properties = query |> Repo.all()
favorites = case conn.assigns.current_user do
nil -> []
current_user ->
Repo.all(
from f in Favorite,
where: f.user_id == ^current_user.id,
select: f.property_id
)
end
render(
conn, "index.html",
properties: properties,
header_text: "Search results",
is_search: true,
favorites: favorites
)
end
end
defmodule PropTrackrWeb.SearchHTML do
use PropTrackrWeb, :html
embed_templates "search_html/*"
embed_templates "properties_html/*"
end
<.header>
Search for properties
</.header>
<.simple_form :let={f} for={@changeset} method="POST" action="/search">
<.input
field={f[:type]}
id="type"
type="select"
label="Type"
options={[{"Rent", :rent}, {"Sell", :sell}, {"Any", :any}]}
required
/>
<.input
field={f[:location]}
id="type"
type="select"
label="Country"
options={@countries}
required
/>
<.input
field={f[:areas]}
id="type"
type="select"
label="City (Area)"
options={@cities}
multiple={true}
required
/>
<.input field={f[:min_price]} id="min_price" type="range" label="Min price" value={@min_price} min={@min_price} max={@max_price} step="10" oninput="document.getElementById('min_price_value').innerText = this.value" required />
<span id="min_price_value"><%= @min_price %></span>
<.input field={f[:max_price]} id="min_price" type="range" label="Max price" value={@max_price} min={@min_price} max={@max_price} step="10" oninput="document.getElementById('max_price_value').innerText = this.value" required />
<span id="max_price_value"><%= @max_price %></span>
<.input field={f[:min_rooms]} id="min_room"s type="range" label="Min rooms" value={@min_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('min_rooms_value').innerText = this.value" required />
<span id="min_rooms_value"><%= @min_rooms %></span>
<.input field={f[:max_rooms]} id="max_room"s type="range" label="Max rooms" value={@max_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('max_rooms_value').innerText = this.value" required />
<span id="max_rooms_value"><%= @max_rooms %></span>
<:actions>
<.button id="search_button">Search</.button>
</:actions>
</.simple_form>
...@@ -46,6 +46,10 @@ defmodule PropTrackrWeb.Router do ...@@ -46,6 +46,10 @@ defmodule PropTrackrWeb.Router do
get "/properties/:reference/edit", PropertiesController, :edit get "/properties/:reference/edit", PropertiesController, :edit
put "/properties/:reference", PropertiesController, :update put "/properties/:reference", PropertiesController, :update
delete "/properties/:reference", PropertiesController, :delete delete "/properties/:reference", PropertiesController, :delete
# Search
get "/search", SearchController, :index
post "/search", SearchController, :create
end end
scope "/api", PropTrackrWeb do scope "/api", PropTrackrWeb do
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment