feat: filter by category/location on click
- add filtering by storage location
This commit is contained in:
@@ -227,9 +227,23 @@ defmodule ComponentsElixirWeb.CategoriesLive do
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<%= if @title_tag == "h3" do %>
|
||||
<h3 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@category.name}</h3>
|
||||
<h3 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?category_id=#{@category.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@category.name}
|
||||
</.link>
|
||||
</h3>
|
||||
<% else %>
|
||||
<h4 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@category.name}</h4>
|
||||
<h4 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?category_id=#{@category.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@category.name}
|
||||
</.link>
|
||||
</h4>
|
||||
<% end %>
|
||||
<span class="text-xs text-base-content/50">
|
||||
({@count_display})
|
||||
@@ -260,9 +274,23 @@ defmodule ComponentsElixirWeb.CategoriesLive do
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<%= if @title_tag == "h3" do %>
|
||||
<h3 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@category.name}</h3>
|
||||
<h3 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?category_id=#{@category.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@category.name}
|
||||
</.link>
|
||||
</h3>
|
||||
<% else %>
|
||||
<h4 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@category.name}</h4>
|
||||
<h4 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?category_id=#{@category.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@category.name}
|
||||
</.link>
|
||||
</h4>
|
||||
<% end %>
|
||||
<%= if @category.description do %>
|
||||
<p class="text-sm text-base-content/60 mt-1">{@category.description}</p>
|
||||
|
||||
@@ -25,6 +25,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
|> assign(:search, "")
|
||||
|> assign(:sort_criteria, "name_asc")
|
||||
|> assign(:selected_category, nil)
|
||||
|> assign(:selected_storage_location, nil)
|
||||
|> assign(:show_advanced_filters, false)
|
||||
|> assign(:offset, 0)
|
||||
|> assign(:components, [])
|
||||
|> assign(:has_more, false)
|
||||
@@ -53,11 +55,19 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
def handle_params(params, _uri, socket) do
|
||||
search = Map.get(params, "search", "")
|
||||
criteria = Map.get(params, "criteria", "name_asc")
|
||||
category_id = parse_filter_id(Map.get(params, "category_id"))
|
||||
storage_location_id = parse_filter_id(Map.get(params, "storage_location_id"))
|
||||
|
||||
# Show advanced filters if storage location is being used
|
||||
show_advanced = not is_nil(storage_location_id)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:search, search)
|
||||
|> assign(:sort_criteria, criteria)
|
||||
|> assign(:selected_category, category_id)
|
||||
|> assign(:selected_storage_location, storage_location_id)
|
||||
|> assign(:show_advanced_filters, show_advanced)
|
||||
|> assign(:offset, 0)
|
||||
|> load_components()}
|
||||
end
|
||||
@@ -88,21 +98,57 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
end
|
||||
|
||||
def handle_event("category_filter", %{"category_id" => ""}, socket) do
|
||||
query_string = build_query_params_without_category(socket)
|
||||
path = if query_string == "", do: "/", else: "/?" <> query_string
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:selected_category, nil)
|
||||
|> assign(:offset, 0)
|
||||
|> load_components()}
|
||||
|> load_components()
|
||||
|> push_patch(to: path)}
|
||||
end
|
||||
|
||||
def handle_event("category_filter", %{"category_id" => category_id}, socket) do
|
||||
category_id = String.to_integer(category_id)
|
||||
query_string = build_query_params_with_category(socket, category_id)
|
||||
path = if query_string == "", do: "/", else: "/?" <> query_string
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:selected_category, category_id)
|
||||
|> assign(:offset, 0)
|
||||
|> load_components()}
|
||||
|> load_components()
|
||||
|> push_patch(to: path)}
|
||||
end
|
||||
|
||||
def handle_event("storage_location_filter", %{"storage_location_id" => ""}, socket) do
|
||||
query_string = build_query_params_with_storage_location(socket, nil)
|
||||
path = if query_string == "", do: "/", else: "/?" <> query_string
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:selected_storage_location, nil)
|
||||
|> assign(:offset, 0)
|
||||
|> load_components()
|
||||
|> push_patch(to: path)}
|
||||
end
|
||||
|
||||
def handle_event("storage_location_filter", %{"storage_location_id" => storage_location_id}, socket) do
|
||||
storage_location_id = String.to_integer(storage_location_id)
|
||||
query_string = build_query_params_with_storage_location(socket, storage_location_id)
|
||||
path = if query_string == "", do: "/", else: "/?" <> query_string
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:selected_storage_location, storage_location_id)
|
||||
|> assign(:offset, 0)
|
||||
|> load_components()
|
||||
|> push_patch(to: path)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_advanced_filters", _params, socket) do
|
||||
{:noreply, assign(socket, :show_advanced_filters, !socket.assigns.show_advanced_filters)}
|
||||
end
|
||||
|
||||
def handle_event("load_more", _params, socket) do
|
||||
@@ -376,6 +422,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
search: socket.assigns.search,
|
||||
sort_criteria: socket.assigns.sort_criteria,
|
||||
category_id: socket.assigns.selected_category,
|
||||
storage_location_id: socket.assigns.selected_storage_location,
|
||||
limit: @items_per_page,
|
||||
offset: socket.assigns.offset
|
||||
]
|
||||
@@ -421,7 +468,57 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
defp build_query_params(socket, overrides) do
|
||||
params = %{
|
||||
search: Map.get(overrides, :search, socket.assigns.search),
|
||||
criteria: Map.get(overrides, :criteria, socket.assigns.sort_criteria)
|
||||
criteria: Map.get(overrides, :criteria, socket.assigns.sort_criteria),
|
||||
category_id: Map.get(overrides, :category_id, socket.assigns.selected_category),
|
||||
storage_location_id: Map.get(overrides, :storage_location_id, socket.assigns.selected_storage_location)
|
||||
}
|
||||
|
||||
params
|
||||
|> Enum.reject(fn {_k, v} -> is_nil(v) or v == "" end)
|
||||
|> URI.encode_query()
|
||||
end
|
||||
|
||||
defp parse_filter_id(nil), do: nil
|
||||
defp parse_filter_id(""), do: nil
|
||||
defp parse_filter_id(id) when is_binary(id) do
|
||||
case Integer.parse(id) do
|
||||
{int_id, ""} -> int_id
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
defp parse_filter_id(id) when is_integer(id), do: id
|
||||
|
||||
defp build_query_params_with_category(socket, category_id) do
|
||||
params = %{
|
||||
search: socket.assigns.search,
|
||||
criteria: socket.assigns.sort_criteria,
|
||||
category_id: category_id,
|
||||
storage_location_id: socket.assigns.selected_storage_location
|
||||
}
|
||||
|
||||
params
|
||||
|> Enum.reject(fn {_k, v} -> is_nil(v) or v == "" end)
|
||||
|> URI.encode_query()
|
||||
end
|
||||
|
||||
defp build_query_params_with_storage_location(socket, storage_location_id) do
|
||||
params = %{
|
||||
search: socket.assigns.search,
|
||||
criteria: socket.assigns.sort_criteria,
|
||||
category_id: socket.assigns.selected_category,
|
||||
storage_location_id: storage_location_id
|
||||
}
|
||||
|
||||
params
|
||||
|> Enum.reject(fn {_k, v} -> is_nil(v) or v == "" end)
|
||||
|> URI.encode_query()
|
||||
end
|
||||
|
||||
defp build_query_params_without_category(socket) do
|
||||
params = %{
|
||||
search: socket.assigns.search,
|
||||
criteria: socket.assigns.sort_criteria,
|
||||
storage_location_id: socket.assigns.selected_storage_location
|
||||
}
|
||||
|
||||
params
|
||||
@@ -558,7 +655,40 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
<% end %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button
|
||||
phx-click="toggle_advanced_filters"
|
||||
class="inline-flex items-center px-3 py-2 border border-base-300 text-sm font-medium rounded-md text-base-content bg-base-100 hover:bg-base-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
|
||||
>
|
||||
<.icon name="hero-adjustments-horizontal" class="w-4 h-4 mr-2" />
|
||||
<%= if @show_advanced_filters, do: "Hide", else: "More" %> Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Filters (Collapsible) -->
|
||||
<%= if @show_advanced_filters do %>
|
||||
<div class="mt-4 p-4 bg-base-100 border border-base-300 rounded-md">
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-base-content mb-2">Storage Location</label>
|
||||
<form phx-change="storage_location_filter">
|
||||
<select
|
||||
name="storage_location_id"
|
||||
class="block w-full px-3 py-2 border border-base-300 rounded-md shadow-sm bg-base-100 text-base-content focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
||||
>
|
||||
<option value="" selected={is_nil(@selected_storage_location)}>All Storage Locations</option>
|
||||
<%= for {location_name, location_id} <- Hierarchical.select_options(@storage_locations, &(&1.parent)) do %>
|
||||
<option value={location_id} selected={@selected_storage_location == location_id}>
|
||||
{location_name}
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Add Component Modal -->
|
||||
|
||||
@@ -362,9 +362,23 @@ defmodule ComponentsElixirWeb.StorageLocationsLive do
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<%= if @title_tag == "h3" do %>
|
||||
<h3 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@location.name}</h3>
|
||||
<h3 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?storage_location_id=#{@location.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@location.name}
|
||||
</.link>
|
||||
</h3>
|
||||
<% else %>
|
||||
<h4 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@location.name}</h4>
|
||||
<h4 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?storage_location_id=#{@location.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@location.name}
|
||||
</.link>
|
||||
</h4>
|
||||
<% end %>
|
||||
<span class="text-xs text-base-content/50">
|
||||
({@count_display})
|
||||
@@ -400,9 +414,23 @@ defmodule ComponentsElixirWeb.StorageLocationsLive do
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<%= if @title_tag == "h3" do %>
|
||||
<h3 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@location.name}</h3>
|
||||
<h3 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?storage_location_id=#{@location.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@location.name}
|
||||
</.link>
|
||||
</h3>
|
||||
<% else %>
|
||||
<h4 class={"#{@text_size} font-medium #{if @depth == 0, do: "text-base-content", else: "text-base-content/80"}"}>{@location.name}</h4>
|
||||
<h4 class={"#{@text_size} font-medium"}>
|
||||
<.link
|
||||
navigate={~p"/?storage_location_id=#{@location.id}"}
|
||||
class={"#{if @depth == 0, do: "text-base-content", else: "text-base-content/80"} hover:text-primary hover:underline"}
|
||||
>
|
||||
{@location.name}
|
||||
</.link>
|
||||
</h4>
|
||||
<% end %>
|
||||
<%= if @location.description do %>
|
||||
<p class="text-sm text-base-content/60 mt-1">{@location.description}</p>
|
||||
|
||||
Reference in New Issue
Block a user