Skip to content

Instantly share code, notes, and snippets.

@mrcwinn
Created May 27, 2023 16:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrcwinn/869a3c9d297ac25e9047c7ce8f8007fb to your computer and use it in GitHub Desktop.
Save mrcwinn/869a3c9d297ac25e9047c7ce8f8007fb to your computer and use it in GitHub Desktop.
RaceSearchLive.ex
defmodule OutandbackWeb.RaceSearchLive do
use OutandbackWeb, :live_view
import Ecto.Query
alias Outandback.Events.Race
@per_page 9
def render(assigns) do
~H"""
<div class="bg-white px-6 lg:px-8">
<div class="mx-auto max-w-2xl md:text-center">
<h2 class="text-3xl font-bold tracking-tight text-gray-900 md:text-5xl">Race Research</h2>
<p class="text-xl font-medium tracking-tight text-gray-600 mt-6">Turn any in-person event into a personalized training plan.</p>
</div>
</div>
<div class="mt-16">
<form class="max-w-md mx-auto">
<.input name="query" placeholder="Race name, place or distance..." value={@query} type="text" phx-change="set_query" class="w-full flex-auto rounded-md border-0 px-3.5 py-2 text-lg text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 mr-2" />
</form>
<ul class="mt-12 lg:grid lg:grid-cols-3 lg:grid-rows-fr lg:gap-6">
<%= for race <- @races do %>
<li>
<div class="h-full cursor-pointer block group relative before:absolute before:-inset-2.5 before:rounded-[20px] before:bg-gray-50 before:opacity-0 hover:before:opacity-100">
<div class="relative aspect-[3/2] overflow-hidden bg-gray-100 rounded-lg ring-1 ring-gray-900/10">
<a href={"/races/#{race.slug}"} class="group-hover:text-blue-600">
<%= unless is_nil(race.latitude) or is_nil(race.longitude) do %>
<img
alt="Image"
width="600"
height="400"
src={"https://api.mapbox.com/styles/v1/mrcwinn/ckaetjl730v8r1im98p1751kj/static/#{race.longitude},#{race.latitude},14.42,0,60/300x200@2x?access_token=pk.eyJ1IjoibXJjd2lubiIsImEiOiI2RjA5b2R3In0.m5u4w0NYCOUaEvBWwYCR-g"}
class="h-auto max-w-full"
/>
<% end %>
</a>
<%= if length(race.distances) > 0 do %>
<div class="top-2 right-2 z-1 absolute font-medium">
<div class="text-gray-900 text-sm space-x-2 pt-2 flex">
<%= for distance <- race.distances do %>
<div class="bg-white ring-1 ring-gray-700 shadow rounded-lg py-1 px-2 text-xs">
<%= distance.distance %><%= distance.distance_unit %>
</div>
<% end %>
</div>
</div>
<% end %>
</div>
<div class="relative p-4 space-y-1 text-gray-600">
<div>
<a href={"/races/#{race.slug}"} class="group-hover:text-blue-600"><%= race.title %></a>
</div>
<%= unless is_nil(race.city) or is_nil(race.state) do %>
<div class="text-sm">
<%= race.city %>, <%= race.state %>
</div>
<% end %>
<%= unless is_nil(race.event_date) do %>
<div class="text-sm">
<%= race.event_date |> Timex.format!("{0M}/{0D}/{YYYY}") %>
</div>
<% end %>
</div>
</div>
</li>
<% end %>
</ul>
<nav
class="my-6 flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<div class="hidden sm:block">
<p class="text-sm text-gray-700">
Showing <span class="font-medium"><%= (@page - 1) * @per_page + 1 %></span> to <span class="font-medium"><%= (@page - 1) * @per_page + 9 %></span> of
<span class="font-medium"><%= @count %></span> results
</p>
</div>
<div class="flex flex-1 justify-between sm:justify-end">
<button
phx-click="prev"
class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Previous
</button>
<button
phx-click="next"
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Next
</button>
</div>
</nav>
</div>
"""
end
def mount(_params, _session, socket) do
query = from r in Race,
limit: 12
count = Outandback.Repo.aggregate(Race, :count)
assigns = [
page: 1,
count: count,
per_page: @per_page,
query: "",
races: paginate(query)
]
{:ok, assign(socket, assigns)}
end
def handle_event("set_query", %{"query" => query_string}, socket) when query_string == "" do
query = build_query(query_string)
assigns = [
page: 1,
query: "",
races: paginate(query)
]
{:noreply, assign(socket, assigns)}
end
def handle_event("set_query", %{"query" => query_string}, socket) when query_string != "" do
query = build_query(query_string)
assigns = [
page: 1,
query: query_string,
races: paginate(query)
]
{:noreply, assign(socket, assigns)}
end
def handle_event("prev", _unsigned_params, socket) do
new_page = socket.assigns.page - 1
new_page = max(new_page, 1)
query = build_query(socket.assigns.query)
assigns = [
page: new_page,
races: paginate(query, new_page),
]
{:noreply, assign(socket, assigns)}
end
def handle_event("next", _unsigned_params, socket) do
new_page = socket.assigns.page + 1
query = build_query(socket.assigns.query)
assigns = [
page: new_page,
races: paginate(query, new_page),
]
{:noreply, assign(socket, assigns)}
end
defp build_query(query_string) do
case query_string do
"" ->
from r in Race
_ ->
from r in Race,
where: fragment("tsv @@ plainto_tsquery('english', ?) OR ? % ? OR ? % ? OR ? % ? OR immutable_array_to_string(?, ' ') % ?",
^query_string,
r.title,
^query_string,
r.city,
^query_string,
r.state,
^query_string,
r.distance_titles,
^query_string
)
end
end
defp paginate(query, page \\ 1) do
query
|> limit(@per_page)
|> offset(^((page - 1) * @per_page))
|> Outandback.Repo.all()
|> Outandback.Repo.preload(:distances)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment