diff --git a/lib/maker_passport/maker.ex b/lib/maker_passport/maker.ex index 0dc4542..367f559 100644 --- a/lib/maker_passport/maker.ex +++ b/lib/maker_passport/maker.ex @@ -18,30 +18,56 @@ defmodule MakerPassport.Maker do [%Profile{}, ...] """ - def list_profiles(skills \\ []) do - Repo.all(Profile) |> Repo.preload([:skills, :user]) - + def list_profiles(filter_params \\ %{}) do query = - Profile - |> join(:left, [p], s in assoc(p, :skills)) - |> maybe_filter_by_skills(skills) - |> preload([:skills, :user]) - |> distinct([p], p.id) + Profile + |> join(:left, [p], s in assoc(p, :skills)) + |> join(:left, [p], l in assoc(p, :location)) + |> join(:left, [p], u in assoc(p, :user)) + |> where([p, ..., u], not is_nil(u.confirmed_at)) + |> maybe_filter_by_country(filter_params) + |> maybe_filter_by_city(filter_params) + |> maybe_filter_by_skills(filter_params) + |> preload([:skills, :user]) + |> distinct([p], p.id) Repo.all(query) end - defp maybe_filter_by_skills(query, []), do: query - - defp maybe_filter_by_skills(query, skills) do + defp maybe_filter_by_skills(query, %{search_skills: skills}) when skills != [], do: query |> where([p, s], s.name in ^skills) |> group_by([p], p.id) |> having([p, s], count(s.id) == ^length(skills)) + + defp maybe_filter_by_skills(query, _), do: query + + def maybe_filter_by_city(query, %{city_search: city}) when city != "" do + query + |> where([p, s, l], ilike(l.city, ^"%#{city}%")) + end + + def maybe_filter_by_city(query, _), do: query + + def maybe_filter_by_country(query, %{country_search: country}) when country != "" do + query + |> where([p, s, l], ilike(l.country, ^"%#{country}%")) end - def list_profiles_by_criteria(criteria) when is_list(criteria) do - query = from(p in Profile, where: not is_nil(p.name)) + def maybe_filter_by_country(query, _), do: query + + + def list_profiles_by_criteria(criteria, filter_params \\ %{}) when is_list(criteria) do + query = Profile + |> where([p], not is_nil(p.name)) + |> join(:left, [p], s in assoc(p, :skills)) + |> join(:left, [p], l in assoc(p, :location)) + |> join(:left, [p], u in assoc(p, :user)) + |> where([p, ..., u], not is_nil(u.confirmed_at)) + |> maybe_filter_by_country(filter_params) + |> maybe_filter_by_city(filter_params) + |> maybe_filter_by_skills(filter_params) + |> distinct([p], p.id) Enum.reduce(criteria, query, fn {:sort, %{sort_by: sort_by, sort_order: sort_order}}, query -> @@ -52,6 +78,9 @@ defmodule MakerPassport.Maker do {:preload, preload}, query -> from q in query, preload: ^preload + + {:current_id, current_id}, query -> + from q in query, where: q.id != ^current_id end) |> Repo.all() |> Repo.preload([:user, :skills]) @@ -346,7 +375,15 @@ defmodule MakerPassport.Maker do Repo.all(from l in Location, where: ilike(l.city, ^"%#{search_text}%")) end - def to_city_list(cities, nil) do + def search_cities("", _), do: [] + + def search_cities(search_text, country) do + Repo.all(from l in Location, where: ilike(l.city, ^"%#{search_text}%") and l.country == ^country) + end + + def to_city_list(cities, selected_city) when is_binary(selected_city) do + cities = Enum.filter(cities, &(&1.city != selected_city)) + cities |> Enum.map(&to_city_tuple/1) |> Enum.sort_by(&elem(&1, 0)) @@ -364,6 +401,31 @@ defmodule MakerPassport.Maker do {location.city, location.id} end + def search_countries(""), do: [] + + def search_countries(search_text) do + Repo.all(from c in Location, where: ilike(c.country, ^"%#{search_text}%"), distinct: c.country) + end + + def to_country_list(countries, selected_country) do + countries = Enum.filter(countries, &(&1.country != selected_country)) + + countries + |> Enum.map(&to_country_tuple/1) + |> Enum.sort_by(&elem(&1, 0)) + end + + defp to_country_tuple(location) do + {get_country_name(location.country), location.country} + end + + def get_country_name(country_code) do + case Countries.get(country_code) do + nil -> "Unknown" + country -> country.name + end + end + @doc """ Gets a single website. diff --git a/lib/maker_passport_web/live/home_live/index.ex b/lib/maker_passport_web/live/home_live/index.ex index abe58d9..bffb4cd 100644 --- a/lib/maker_passport_web/live/home_live/index.ex +++ b/lib/maker_passport_web/live/home_live/index.ex @@ -9,12 +9,7 @@ defmodule MakerPassportWeb.HomeLive.Index do @impl true def mount(_params, _session, socket) do - latest_profiles = - Maker.list_profiles_by_criteria( - limit: 4, - sort: %{sort_by: :updated_at, sort_order: :desc}, - preload: [:skills] - ) + latest_profiles = fetch_latest_profiles(socket.assigns.current_user) socket = socket @@ -22,4 +17,21 @@ defmodule MakerPassportWeb.HomeLive.Index do {:ok, socket} end + + defp fetch_latest_profiles(nil) do + Maker.list_profiles_by_criteria( + limit: 4, + sort: %{sort_by: :updated_at, sort_order: :desc}, + preload: [:skills] + ) + end + + defp fetch_latest_profiles(user) do + Maker.list_profiles_by_criteria( + limit: 4, + sort: %{sort_by: :updated_at, sort_order: :desc}, + preload: [:skills], + current_id: user.id + ) + end end diff --git a/lib/maker_passport_web/live/profile_live/index.ex b/lib/maker_passport_web/live/profile_live/index.ex index 1620455..2475370 100644 --- a/lib/maker_passport_web/live/profile_live/index.ex +++ b/lib/maker_passport_web/live/profile_live/index.ex @@ -22,9 +22,10 @@ defmodule MakerPassportWeb.ProfileLive.Index do socket = socket |> assign(:page_title, "Maker Profiles") - |> assign(:search_skills, []) + |> assign(:filter_params, %{search_skills: [], country_search: "", city_search: ""}) |> assign(:no_skills_results, false) |> assign(:profile, nil) + |> assign(:form, to_form(%{}, as: "filter_params")) |> stream(:profiles, profiles) {:ok, socket} @@ -41,20 +42,33 @@ defmodule MakerPassportWeb.ProfileLive.Index do end @impl true - def handle_info({:typeahead, {name, _}, _}, socket) do + def handle_info( + {:typeahead, {country_name, country_code}, "country-search-picker" = id}, + socket + ) do socket = socket - |> update(:search_skills, fn skills -> [name | skills] end) + |> push_event(%{id: id, label: country_name}) + |> update(:filter_params, fn params -> + Map.put(params, :country_search, country_code) + |> Map.put(:city_search, "") + end) - profiles = - case socket.assigns.current_user do - nil -> - Maker.list_profiles(socket.assigns.search_skills) + profiles = filter_profiles(socket.assigns.current_user, socket.assigns.filter_params) - user -> - Maker.list_profiles(socket.assigns.search_skills) - |> Enum.reject(fn profile -> profile.user && profile.user.id == user.id end) - end + {:noreply, stream(socket, :profiles, profiles)} + end + + @impl true + def handle_info({:typeahead, {value, _}, id}, socket) do + socket = + socket + |> push_event(%{id: id, label: value}) + |> update(:filter_params, fn params -> + add_filter_params(id, params, value) + end) + + profiles = filter_profiles(socket.assigns.current_user, socket.assigns.filter_params) socket = socket @@ -65,36 +79,98 @@ defmodule MakerPassportWeb.ProfileLive.Index do end @impl true - def handle_event("delete", %{"id" => id}, socket) do - profile = Maker.get_profile!(id) - {:ok, _} = Maker.delete_profile(profile) + def handle_event( + "filter-profiles", + %{"filter_params" => %{"country_search" => ""}}, + %{assigns: %{filter_params: %{country_search: country_search}}} = socket + ) + when country_search != "" do + handle_remove_search(socket, [:city_search, :country_search]) + end - {:noreply, stream_delete(socket, :profiles, profile)} + @impl true + def handle_event( + "filter-profiles", + %{"filter_params" => %{"city_search" => ""}}, + %{assigns: %{filter_params: %{city_search: city_search}}} = socket + ) when city_search != "" do + handle_remove_search(socket, [:city_search]) end + @impl true def handle_event("remove-skill", %{"skill_name" => skill_name}, socket) do - updated_skills = - Enum.filter(socket.assigns.search_skills, fn skill -> skill != skill_name end) - - profiles = - case socket.assigns.current_user do - nil -> - Maker.list_profiles(updated_skills) - - user -> - Maker.list_profiles(updated_skills) - |> Enum.reject(fn profile -> profile.user && profile.user.id == user.id end) - end + filter_params = socket.assigns.filter_params + updated_skills = Enum.filter(filter_params.search_skills, fn skill -> skill != skill_name end) + filter_params = %{filter_params | search_skills: updated_skills} + profiles = filter_profiles(socket.assigns.current_user, filter_params) socket = socket |> assign(:no_skills_results, profiles == []) - |> assign(:search_skills, updated_skills) + |> assign(:filter_params, filter_params) |> stream(:profiles, profiles) {:noreply, socket} end + @impl true + def handle_event("remove-country", _params, socket) do + handle_remove_search(socket, [:city_search, :country_search]) + end + + @impl true + def handle_event("remove-city", _params, socket) do + handle_remove_search(socket, [:city_search]) + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + profile = Maker.get_profile!(id) + {:ok, _} = Maker.delete_profile(profile) + + {:noreply, stream_delete(socket, :profiles, profile)} + end + + @impl true + def handle_event(_event, _params, socket) do + {:noreply, socket} + end + + defp add_filter_params("city-search-picker", filter_params, value) do + Map.put(filter_params, :city_search, value) + end + + defp add_filter_params("skills-search-picker", filter_params, value) do + Map.update(filter_params, :search_skills, [value], fn skills -> [value | skills] end) + end + + def filter_profiles(current_user, filter_params) do + case current_user do + nil -> + Maker.list_profiles_by_criteria([{:sort, %{sort_by: :updated_at, sort_order: :desc}}], filter_params) + + user -> + Maker.list_profiles(filter_params) + |> Enum.reject(fn profile -> profile.user && profile.user.id == user.id end) + end + end + + defp push_event(socket, %{id: id} = params) when id != "skills-search-picker" do + push_event(socket, "set-input-value", params) + end + + defp push_event(socket, _), do: socket + + defp handle_remove_search(socket, fields) do + filter_params = + Enum.reduce(fields, socket.assigns.filter_params, fn field, acc -> + Map.put(acc, field, "") + end) + + profiles = filter_profiles(socket.assigns.current_user, filter_params) + {:noreply, stream(socket, :profiles, profiles) |> assign(:filter_params, filter_params)} + end + defp remove_selected_skill(skills, selected_skills) do Enum.filter(skills, fn {skill, _} -> skill not in selected_skills end) end diff --git a/lib/maker_passport_web/live/profile_live/index.html.heex b/lib/maker_passport_web/live/profile_live/index.html.heex index 00d6ba9..71ae1e6 100644 --- a/lib/maker_passport_web/live/profile_live/index.html.heex +++ b/lib/maker_passport_web/live/profile_live/index.html.heex @@ -2,30 +2,93 @@
<.icon :if={@profile.location} name="hero-map-pin" class="w-5 h-5" /> <%= @profile.location && - get_country_name(@profile.location.country) <> " . " <> @profile.location.city %> + Maker.get_country_name(@profile.location.country) <> " . " <> @profile.location.city %>
<%= @profile.bio %>