diff --git a/lib/sanbase_web/components/table.ex b/lib/sanbase_web/components/table.ex index 49a4d2c47c..be4259d504 100644 --- a/lib/sanbase_web/components/table.ex +++ b/lib/sanbase_web/components/table.ex @@ -39,6 +39,15 @@ defmodule SanbaseWeb.TableComponent do <% end %> + + """ end @@ -71,10 +80,12 @@ defmodule SanbaseWeb.TableComponent do <.form :let={f} + method="get" for={%{}} as={:search} action={Routes.generic_path(SanbaseWeb.Endpoint, :search, resource: @resource)} > + <%= hidden_input(f, :resource, value: @resource) %> <%= text_input(f, :generic_search, value: @search_value, class: @@ -100,3 +111,71 @@ defmodule SanbaseWeb.TableComponent do """ end end + +defmodule SanbaseWeb.PaginationComponent do + use Phoenix.Component + + alias SanbaseWeb.Router.Helpers, as: Routes + + def pagination(assigns) do + ~H""" +
+ <.pagination_buttons + resource={@resource} + rows_count={@rows_count} + page_size={@page_size} + current_page={@current_page} + action={@action} + search_text={@search_text} + /> + + Showing <%= @current_page * @page_size + 1 %> to <%= Enum.min([ + (@current_page + 1) * @page_size, + @rows_count + ]) %> of <%= @rows_count %> entries + +
+ """ + end + + def pagination_buttons(assigns) do + ~H""" +
+ <%= unless @current_page == 0 do %> + <.link + href={pagination_path(@resource, @action, @search_text, @current_page - 1)} + class="px-4 py-2 mx-1 bg-gray-200 rounded hover:bg-gray-300" + > + Previous + + <% end %> + <%= unless @current_page >= div(@rows_count - 1, @page_size) do %> + <.link + href={pagination_path(@resource, @action, @search_text, @current_page + 1)} + class="px-4 py-2 mx-1 bg-gray-200 rounded hover:bg-gray-300" + > + Next + + <% end %> +
+ """ + end + + defp pagination_path(resource, action, search_text, page_number) do + case action do + :index -> + Routes.generic_path(SanbaseWeb.Endpoint, action, %{resource: resource, page: page_number}) + + :search -> + Routes.generic_path(SanbaseWeb.Endpoint, action, %{ + "resource" => resource, + "page" => page_number, + "search" => %{ + "generic_search" => search_text, + "resource" => resource, + "page" => page_number + } + }) + end + end +end diff --git a/lib/sanbase_web/controllers/generic_controller.ex b/lib/sanbase_web/controllers/generic_controller.ex index fb18e1e3ee..5196fc4640 100644 --- a/lib/sanbase_web/controllers/generic_controller.ex +++ b/lib/sanbase_web/controllers/generic_controller.ex @@ -8,22 +8,32 @@ defmodule SanbaseWeb.GenericController do @resource_module_map SanbaseWeb.GenericAdmin.resource_module_map() - def index(conn, %{"resource" => resource}) do - render(conn, "index.html", table: resource_to_table_params(resource)) + def index(conn, %{"resource" => resource} = params) do + page = params["page"] || 0 + page_size = params["page_size"] || 10 + + render(conn, "index.html", + table: resource_to_table_params(resource, %{page: page, page_size: page_size}) + ) end def index(conn, _) do render(conn, "error.html") end - def search(conn, %{"resource" => resource, "search" => %{"generic_search" => search_text}}) do + def search( + conn, + %{"search" => %{"generic_search" => search_text, "resource" => resource}} = params + ) do module = module_from_resource(resource) preloads = @resource_module_map[resource][:preloads] || [] + page = to_integer(params["page"] || 0) + page_size = to_integer(params["page_size"] || 10) - rows = + {total_rows, paginated_rows} = case parse_field_value(search_text) do {:ok, field, value} -> - search_by_field_value(module, field, value, preloads) + search_by_field_value(module, field, value, preloads, page, page_size) :error -> case Integer.parse(search_text) do @@ -32,7 +42,16 @@ defmodule SanbaseWeb.GenericController do end end - render(conn, "index.html", table: resource_to_table_params(resource, rows)) + render(conn, "index.html", + table: + resource_to_table_params(resource, %{ + total_rows: total_rows, + rows: paginated_rows, + page: page, + page_size: page_size, + search_text: search_text + }) + ) end def show(conn, %{"resource" => resource, "id" => id}) do @@ -95,11 +114,14 @@ defmodule SanbaseWeb.GenericController do def module_from_resource(resource), do: @resource_module_map[resource][:module] - def resource_to_table_params(resource, rows \\ nil) do + def resource_to_table_params(resource, params) do name = String.capitalize(resource) module = @resource_module_map[resource][:module] preloads = @resource_module_map[resource][:preloads] || [] funcs = @resource_module_map[resource][:funcs] || %{} + rows = params[:rows] + page = to_integer(params[:page]) + page_size = to_integer(params[:page_size]) index_fields = case @resource_module_map[resource][:index_fields] do @@ -108,13 +130,48 @@ defmodule SanbaseWeb.GenericController do fields when is_list(fields) -> fields end + total_count = + case rows do + nil -> Repo.aggregate(module, :count, :id) + _ -> params[:total_rows] + end + + action = + case rows do + nil -> :index + _ -> :search + end + + offset = page * page_size + + fetched_rows = + case rows do + nil -> + Repo.all( + from(m in module, + order_by: [desc: m.id], + preload: ^preloads, + limit: ^page_size, + offset: ^offset + ) + ) + + _ -> + rows + end + %{ resource: resource, resource_name: name, - rows: rows || all(module, preloads), + rows: fetched_rows, + rows_count: total_count, fields: index_fields, funcs: funcs, - actions: @resource_module_map[resource][:actions] + actions: @resource_module_map[resource][:actions], + current_page: page, + page_size: page_size, + action: action, + search_text: params[:search_text] || "" } end @@ -152,26 +209,57 @@ defmodule SanbaseWeb.GenericController do end def parse_field_value(str) do - case Regex.run(~r/(\w+)\s*=\s*(\w+)/, str) do + case Regex.run(~r/(\S+)\s*=\s*(\S+)/, str) do [_, field, value] -> {:ok, field, value} _ -> :error end end - def search_by_field_value(module, field, value, preloads) do + def search_by_field_value(module, field, value, preloads, page, page_size) do case Integer.parse(value) do {id, ""} -> search_by_id(module, id, preloads) _ -> value = String.trim(value) - search_text = "%" <> value <> "%" + + value = "%" <> value <> "%" field = String.to_existing_atom(field) - from(u in module, - where: like(field(u, ^field), ^search_text) - ) - |> Repo.all() + field_type = module.__schema__(:type, field) + + query = + if field_type == :string do + from(u in module, + where: like(field(u, ^field), ^value), + preload: ^preloads, + order_by: [desc: u.id] + ) + else + from(u in module, + where: field(u, ^field) == ^value, + preload: ^preloads, + order_by: [desc: u.id] + ) + end + + total_rows = Repo.aggregate(query, :count, :id) + + paginated_rows = + query + |> limit(^page_size) + |> offset(^(page_size * page)) + |> Repo.all() + + {total_rows, paginated_rows} + end + end + + def to_integer(value) do + case value do + nil -> nil + value when is_integer(value) -> value + value when is_binary(value) -> String.to_integer(value) end end end diff --git a/lib/sanbase_web/router.ex b/lib/sanbase_web/router.ex index b725dad595..fb4adef30e 100644 --- a/lib/sanbase_web/router.ex +++ b/lib/sanbase_web/router.ex @@ -68,7 +68,7 @@ defmodule SanbaseWeb.Router do resources("/sheets_templates", SheetsTemplateController) resources("/webinars", WebinarController) resources("/custom_plans", CustomPlanController) - post("/generic/search", GenericController, :search) + get("/generic/search", GenericController, :search) get("/generic/show_action", GenericController, :show_action) resources("/generic", GenericController) end diff --git a/lib/sanbase_web/templates/generic_html/index.html.heex b/lib/sanbase_web/templates/generic_html/index.html.heex index 893c10ff31..316e366f8a 100644 --- a/lib/sanbase_web/templates/generic_html/index.html.heex +++ b/lib/sanbase_web/templates/generic_html/index.html.heex @@ -1,2 +1,13 @@ - \ No newline at end of file +