Packages
literature
0.1.6
0.5.1
0.5.0
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
retired
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.32
0.1.30
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A simple CMS / Blog
Current section
Files
Jump to
Current section
Files
lib/literature/components/table_component.ex
defmodule Literature.TableComponent do
@moduledoc false
use Literature.Web, :live_component
import Scrivener.PhoenixView
alias Plug.Conn.Query
@impl Phoenix.LiveComponent
def update(assigns, socket) do
socket
|> assign(assigns)
|> assign_new(:params, fn -> Map.new() end)
|> then(&{:ok, &1})
end
@impl Phoenix.LiveComponent
def render(assigns) do
~H"""
<div class="col-span-4 relative sm:rounded-lg w-full">
<div class="flex items-center justify-between mb-5">
<div class="w-1/2 space-y-3">
<form phx-target={@myself} phx-change="search" class="flex items-center border-gray-300 border rounded-lg text-gray-900 pl-2.5">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
<path fill-rule="evenodd" d="M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z" clip-rule="evenodd" />
</svg>
<%= search_input :search, :q, value: @params["q"], class: "text-sm rounded-lg focus:outline-none block w-full p-2.5", placeholder: "Find", autofocus: true, phx_debounce: 300 %>
</form>
<%= filter_status Enum.into(@columns, %{}), assigns %>
</div>
<%= live_redirect to: @new_path, class: "text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-3 py-2.5 text-center mr-3 md:mr-0 flex items-center" do %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
<path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zM12.75 9a.75.75 0 00-1.5 0v2.25H9a.75.75 0 000 1.5h2.25V15a.75.75 0 001.5 0v-2.25H15a.75.75 0 000-1.5h-2.25V9z" clip-rule="evenodd" />
</svg>
<span class="flex-1 ml-2 whitespace-nowrap">Create new</span>
<% end %>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<%= for column <- @columns do %>
<th scope="col" class="py-3 px-6">
<%= table_sort(@base_path, @params, column) %>
</th>
<% end %>
<th scope="col" class="py-3 px-6">
Actions
</th>
</tr>
</thead>
<tbody>
<%= if Enum.any?(@items) do %>
<%= for item <- @items do %>
<tr class="bg-white border-b hover:bg-gray-50">
<%= for {field, _} <- @columns do %>
<td scope="row" class="py-4 px-6 font-medium text-gray-900 whitespace-nowrap">
<%= render_item item, field %>
</td>
<% end %>
<td class="py-4 px-6">
<.actions item={item} base_path={@base_path} />
</td>
</tr>
<% end %>
<% else %>
<tr>
<td colspan={Enum.count(@columns) + 1}>
<p class="py-5 text-center font-medium">No data found. Create a new one!</p>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<%= paginate @socket, @page, fn socket, [page: page] -> literature_dashboard_path(socket, @live_action, @slug, page, @params) end %>
</div>
"""
end
@impl Phoenix.LiveComponent
def handle_event(
"search",
%{"search" => search},
%{assigns: %{params: params, live_action: live_action, slug: slug}} = socket
) do
params
|> Map.delete("page")
|> Map.merge(search)
|> then(&push_patch(socket, to: literature_dashboard_path(socket, live_action, slug, &1)))
|> then(&{:noreply, &1})
end
@impl Phoenix.LiveComponent
def handle_event(
"filter",
%{"filter" => filter},
%{assigns: %{params: params, live_action: live_action, slug: slug}} = socket
) do
params
|> Map.delete("page")
|> Map.merge(filter)
|> then(&push_patch(socket, to: literature_dashboard_path(socket, live_action, slug, &1)))
|> then(&{:noreply, &1})
end
defp actions(assigns) do
assigns =
Map.put(assigns, :base_path, String.replace_suffix(assigns.base_path, "/page/1", ""))
~H"""
<div class="flex items-center space-x-2">
<%= live_patch to: "#{@base_path}/#{@item.slug}/edit", id: "edit-#{@item.id}", class: "hover:text-primary-600 transition duration-300 ease-in-out" do %>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
<% end %>
<%= link to: "#", phx_click: "open_delete_modal", phx_value_id: @item.id, id: "delete-#{@item.id}", class: "hover:text-red-600 transition duration-300 ease-in-out" do %>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
<% end %>
</div>
"""
end
defp render_item(item, :status = field) do
label = Map.get(item, field)
classes =
case label do
"scheduled" -> "bg-yellow-100 text-yellow-800"
"published" -> "bg-primary-100 text-primary-800"
_ -> "bg-gray-100 text-gray-800"
end
content_tag(:span, label,
class: "capitalize text-xs font-semibold mr-2 px-2.5 py-1 rounded-lg #{classes}"
)
end
defp render_item(item, :published_at = field) do
Map.get(item, field)
|> case do
nil -> ""
datetime -> Timex.format!(datetime, "%d %b %Y at %I:%M %p", :strftime)
end
end
defp render_item(item, :visibility = field) do
visible? = Map.get(item, field)
label = (visible? && "Public") || "Private"
classes = (visible? && "bg-green-100 text-green-800") || "bg-red-100 text-red-800"
content_tag(:span, label,
class: "text-xs font-semibold mr-2 px-2.5 py-1 rounded-lg #{classes}"
)
end
defp render_item(item, field), do: Map.get(item, field)
defp filter_status(%{published_at: _}, %{params: params} = assigns) do
params = Map.put_new(params, "status", "all")
assigns = Map.put(assigns, :params, params)
~H"""
<form phx-target={@myself} phx-change="filter">
<ul class="flex items-center text-gray-600 text-sm font-semibold">
<.radio_button label="All" value="all" status={@params["status"]} />
<.radio_button label="Drafts" value="drafts" status={@params["status"]} />
<.radio_button label="Scheduled" value="scheduled" status={@params["status"]} />
<.radio_button label="Published" value="published" status={@params["status"]} />
</ul>
</form>
"""
end
defp filter_status(%{visibility: _}, %{params: params} = assigns) do
params = Map.put_new(params, "status", "all")
assigns = Map.put(assigns, :params, params)
~H"""
<form phx-target={@myself} phx-change="filter">
<ul class="flex items-center text-gray-600 text-sm font-semibold">
<.radio_button label="All" value="all" status={@params["status"]} />
<.radio_button label="Public" value="public" status={@params["status"]} />
<.radio_button label="Private" value="private" status={@params["status"]} />
</ul>
</form>
"""
end
defp filter_status(_, _), do: nil
defp radio_button(assigns) do
~H"""
<li class="hover:bg-gray-100 w-full rounded transition duration-300 ease-in-out">
<%= label class: "flex items-center cursor-pointer p-3" do %>
<%= radio_button :filter, :status, @value, class: "w-4 h-4 border-gray-300 text-primary-700 bg-primary-700", checked: @status == @value %>
<span class="px-2"><%= @label %></span>
<% end %>
</li>
"""
end
defp table_sort(base_path, params, {field, text}) do
base_path = String.replace_suffix(base_path, "/page/1", "/page/#{params["page"] || 1}")
direction = params["sort_direction"]
sort_direction =
if params["sort_field"] == to_string(field), do: reverse(direction), else: "desc"
opts = %{
sort_field: field,
sort_direction: sort_direction
}
live_patch(text, to: "#{base_path}?#{query_string(params, opts)}")
end
defp query_string(params, opts) do
params = params |> Query.encode() |> URI.decode_query()
opts = %{
"sort_field" => opts[:sort_field] || params["sort_field"] || nil,
"sort_direction" => opts[:sort_direction] || params["sort_direction"] || nil
}
params
|> Map.delete("page")
|> Map.merge(opts)
|> Enum.filter(fn {_, v} -> v != nil end)
|> Enum.into(%{})
|> URI.encode_query()
end
defp reverse("desc"), do: "asc"
defp reverse(_), do: "desc"
end