mirror of
https://github.com/ClaperCo/Claper.git
synced 2026-09-01 19:49:11 +02:00
Add pagination tests for users and events in Admin module
This commit is contained in:
@@ -3,6 +3,19 @@ defmodule Claper.Accounts.User do
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@derive {
|
||||
Flop.Schema,
|
||||
max_limit: 100,
|
||||
default_limit: 20,
|
||||
pagination_types: [:page],
|
||||
filterable: [:email, :role_id],
|
||||
sortable: [:email, :role_id, :confirmed_at, :inserted_at],
|
||||
default_order: %{
|
||||
order_by: [:inserted_at],
|
||||
order_directions: [:desc]
|
||||
}
|
||||
}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: integer(),
|
||||
uuid: Ecto.UUID.t(),
|
||||
|
||||
@@ -316,128 +316,46 @@ defmodule Claper.Admin do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a paginated list of users.
|
||||
Returns a paginated list of users using Flop.
|
||||
|
||||
## Options
|
||||
|
||||
* `:page` - The page number (default: 1)
|
||||
* `:per_page` - The number of users per page (default: 20)
|
||||
* `:search` - Search term for filtering users by email
|
||||
* `:role` - Filter users by role name
|
||||
Accepts Flop params for filtering, sorting, and pagination.
|
||||
See `Claper.Accounts.User` Flop.Schema config for available fields.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_users_paginated(%{page: 1, per_page: 10})
|
||||
%{entries: [%User{}, ...], page_number: 1, page_size: 10, total_entries: 20, total_pages: 2}
|
||||
iex> list_users_paginated(%{"page" => 1, "page_size" => 10})
|
||||
{[%User{}, ...], %Flop.Meta{}}
|
||||
|
||||
"""
|
||||
def list_users_paginated(params \\ %{}) do
|
||||
page = Map.get(params, "page", 1)
|
||||
per_page = Map.get(params, "per_page", 20)
|
||||
search = Map.get(params, "search", "")
|
||||
role = Map.get(params, "role", "")
|
||||
|
||||
query =
|
||||
User
|
||||
|> where([u], is_nil(u.deleted_at))
|
||||
|> preload(:role)
|
||||
|> join(:left, [u], r in assoc(u, :role), as: :role)
|
||||
|
||||
query =
|
||||
if search != "" do
|
||||
query |> where([u], ilike(u.email, ^"%#{search}%"))
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if role != "" do
|
||||
query |> join(:inner, [u], r in assoc(u, :role), on: r.name == ^role)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query = query |> order_by([u], desc: u.inserted_at)
|
||||
|
||||
Repo.paginate(query, page: page, page_size: per_page)
|
||||
Flop.validate_and_run!(query, params, for: User, replace_invalid_params: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a paginated list of events.
|
||||
Returns a paginated list of events using Flop.
|
||||
|
||||
## Options
|
||||
|
||||
* `:page` - The page number (default: 1)
|
||||
* `:per_page` - The number of events per page (default: 20)
|
||||
* `:search` - Search term for filtering events by name
|
||||
* `:status` - Filter events by status (upcoming, past)
|
||||
* `:start_date` - Filter events by start date
|
||||
* `:end_date` - Filter events by end date
|
||||
* `:creator_id` - Filter events by creator ID
|
||||
Accepts Flop params for filtering, sorting, and pagination.
|
||||
See `Claper.Events.Event` Flop.Schema config for available fields.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_events_paginated(%{page: 1, per_page: 10})
|
||||
%{entries: [%Event{}, ...], page_number: 1, page_size: 10, total_entries: 20, total_pages: 2}
|
||||
iex> list_events_paginated(%{"page" => 1, "page_size" => 10})
|
||||
{[%Event{}, ...], %Flop.Meta{}}
|
||||
|
||||
"""
|
||||
def list_events_paginated(params \\ %{}) do
|
||||
page = Map.get(params, "page", 1)
|
||||
per_page = Map.get(params, "per_page", 20)
|
||||
search = Map.get(params, "search", "")
|
||||
status = Map.get(params, "status", "")
|
||||
start_date = Map.get(params, "start_date", nil)
|
||||
end_date = Map.get(params, "end_date", nil)
|
||||
creator_id = Map.get(params, "creator_id", nil)
|
||||
|
||||
query =
|
||||
Event
|
||||
|> join(:left, [e], u in assoc(e, :user), as: :user)
|
||||
|> preload(:user)
|
||||
|
||||
query =
|
||||
if search != "" do
|
||||
query |> where([e], ilike(e.name, ^"%#{search}%"))
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
case status do
|
||||
"upcoming" ->
|
||||
now = NaiveDateTime.utc_now()
|
||||
query |> where([e], e.started_at > ^now)
|
||||
|
||||
"past" ->
|
||||
now = NaiveDateTime.utc_now()
|
||||
query |> where([e], e.started_at <= ^now)
|
||||
|
||||
_ ->
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if start_date do
|
||||
query |> where([e], e.started_at >= ^start_date)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if end_date do
|
||||
query |> where([e], e.started_at <= ^end_date)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if creator_id do
|
||||
query |> where([e], e.user_id == ^creator_id)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query = query |> order_by([e], desc: e.started_at)
|
||||
|
||||
Repo.paginate(query, page: page, page_size: per_page)
|
||||
Flop.validate_and_run!(query, params, for: Event, replace_invalid_params: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -5,6 +5,8 @@ defmodule Claper.Audit.Log do
|
||||
@derive {
|
||||
Flop.Schema,
|
||||
max_limit: 100,
|
||||
default_limit: 20,
|
||||
pagination_types: [:page],
|
||||
filterable: [:action, :user_email],
|
||||
sortable: [:inserted_at, :action],
|
||||
default_order: %{
|
||||
|
||||
@@ -2,6 +2,28 @@ defmodule Claper.Events.Event do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@derive {
|
||||
Flop.Schema,
|
||||
max_limit: 100,
|
||||
default_limit: 20,
|
||||
pagination_types: [:page],
|
||||
filterable: [:name, :code, :user_email],
|
||||
sortable: [:name, :code, :started_at, :expired_at, :audience_peak],
|
||||
default_order: %{
|
||||
order_by: [:started_at],
|
||||
order_directions: [:desc]
|
||||
},
|
||||
adapter_opts: [
|
||||
join_fields: [
|
||||
user_email: [
|
||||
binding: :user,
|
||||
field: :email,
|
||||
path: [:user, :email]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: integer(),
|
||||
uuid: Ecto.UUID.t(),
|
||||
|
||||
@@ -495,4 +495,84 @@ defmodule ClaperWeb.CoreComponents do
|
||||
def translate_errors(errors, field) when is_list(errors) do
|
||||
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a DaisyUI pagination component from a Flop.Meta struct.
|
||||
|
||||
## Attributes
|
||||
|
||||
* `meta` - A `Flop.Meta` struct with pagination info.
|
||||
* `path` - The base path for pagination links.
|
||||
"""
|
||||
attr :meta, :map, required: true
|
||||
attr :path, :string, required: true
|
||||
|
||||
def pagination(assigns) do
|
||||
meta = assigns.meta
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:current_page, meta.current_page || 1)
|
||||
|> assign(:total_pages, meta.total_pages || 1)
|
||||
|> assign(:has_previous?, meta.has_previous_page?)
|
||||
|> assign(:has_next?, meta.has_next_page?)
|
||||
|> assign(:page_range, page_range(meta.current_page || 1, meta.total_pages || 1))
|
||||
|
||||
~H"""
|
||||
<nav :if={@total_pages > 1} class="flex items-center gap-3" aria-label="Pagination">
|
||||
<span class="text-sm text-base-content/60">
|
||||
{gettext("Page %{current} of %{total}", current: @current_page, total: @total_pages)}
|
||||
</span>
|
||||
<div class="join">
|
||||
<.link
|
||||
patch={page_path(@path, @meta, @current_page - 1)}
|
||||
class={[
|
||||
"join-item btn btn-ghost btn-sm",
|
||||
!@has_previous? && "btn-disabled"
|
||||
]}
|
||||
aria-label={gettext("Previous page")}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</.link>
|
||||
<.link
|
||||
:for={page <- @page_range}
|
||||
patch={page_path(@path, @meta, page)}
|
||||
class={[
|
||||
"join-item btn btn-sm",
|
||||
if(page == @current_page, do: "btn-active", else: "btn-ghost")
|
||||
]}
|
||||
>
|
||||
{page}
|
||||
</.link>
|
||||
<.link
|
||||
patch={page_path(@path, @meta, @current_page + 1)}
|
||||
class={[
|
||||
"join-item btn btn-ghost btn-sm",
|
||||
!@has_next? && "btn-disabled"
|
||||
]}
|
||||
aria-label={gettext("Next page")}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</.link>
|
||||
</div>
|
||||
</nav>
|
||||
"""
|
||||
end
|
||||
|
||||
defp page_path(path, meta, page) do
|
||||
Flop.Phoenix.build_path(path, %{meta | flop: %{meta.flop | page: page}})
|
||||
end
|
||||
|
||||
defp page_range(_current, total) when total <= 5, do: 1..total
|
||||
|
||||
defp page_range(current, total) do
|
||||
start = max(1, current - 2)
|
||||
stop = min(total, start + 4)
|
||||
start = max(1, stop - 4)
|
||||
start..stop
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,49 +1,22 @@
|
||||
<h1 class="text-3xl font-bold mb-24">{@page_title}</h1>
|
||||
<h1 class="text-3xl font-bold mb-6">{@page_title}</h1>
|
||||
|
||||
<%= if @live_action == :index do %>
|
||||
<div class="flex flex-col sm:flex-row items-stretch gap-3 @container/controls">
|
||||
<.form
|
||||
for={@form}
|
||||
class="grow flex flex-col sm:flex-row gap-3 sm:[&>div]:basis-[250px]"
|
||||
phx-change="filter-logs"
|
||||
>
|
||||
<Flop.Phoenix.filter_fields :let={i} form={@form} fields={@fields}>
|
||||
<ClaperWeb.CoreComponents.input field={i.field} label={i.label} type={i.type} {i.rest} />
|
||||
</Flop.Phoenix.filter_fields>
|
||||
</.form>
|
||||
<Flop.Phoenix.pagination
|
||||
meta={@meta}
|
||||
path={~p"/admin/audit_logs"}
|
||||
class="flex [&>ul]:mr-3 justify-end"
|
||||
page_list_attrs={[class: "hidden @4xl/controls:flex"]}
|
||||
page_list_item_attrs={[
|
||||
class:
|
||||
"grid place-content-stretch min-h-10 min-w-10 border-t border-r border-b border-gray-300 first:border-l first:rounded-l-full last:rounded-r-full first:[&>*]:rounded-l-full last:[&>*]:rounded-r-full text-gray-500 bg-white [&>*]:grid [&>*]:place-content-center"
|
||||
]}
|
||||
page_link_attrs={[
|
||||
class: "hover:text-black hover:bg-gray-200"
|
||||
]}
|
||||
current_page_link_attrs={[
|
||||
class: "pointer-events-none font-bold text-black"
|
||||
]}
|
||||
>
|
||||
<:previous attrs={[
|
||||
class:
|
||||
"order-1 border border-gray-300 rounded-l-full h-10 p-5 bg-white flex items-center justify-center aria-disabled:text-gray-300 hover:not-aria-disabled:bg-gray-200"
|
||||
]}>
|
||||
{gettext("Previous")}
|
||||
</:previous>
|
||||
<:next attrs={[
|
||||
class:
|
||||
"order-2 border-t border-r border-b border-gray-300 rounded-r-full h-10 p-5 bg-white flex items-center justify-center aria-disabled:text-gray-300 hover:not-aria-disabled:bg-gray-200"
|
||||
]}>
|
||||
{gettext("Next")}
|
||||
</:next>
|
||||
<:ellipsis>
|
||||
<span aria-hidden="true" class="text-gray-300">•••</span>
|
||||
</:ellipsis>
|
||||
</Flop.Phoenix.pagination>
|
||||
</div>
|
||||
<.form
|
||||
for={@form}
|
||||
class="flex flex-col sm:flex-row gap-3 sm:[&>div]:basis-[250px]"
|
||||
phx-change="filter-logs"
|
||||
>
|
||||
<Flop.Phoenix.filter_fields :let={i} form={@form} fields={@fields}>
|
||||
<ClaperWeb.CoreComponents.input
|
||||
field={i.field}
|
||||
label={i.label}
|
||||
type={i.type}
|
||||
phx-debounce="300"
|
||||
{i.rest}
|
||||
/>
|
||||
</Flop.Phoenix.filter_fields>
|
||||
</.form>
|
||||
|
||||
<Flop.Phoenix.table
|
||||
items={@logs}
|
||||
meta={@meta}
|
||||
@@ -91,6 +64,10 @@
|
||||
</.link>
|
||||
</:action>
|
||||
</Flop.Phoenix.table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<ClaperWeb.CoreComponents.pagination meta={@meta} path={~p"/admin/audit_logs"} />
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if @live_action == :show do %>
|
||||
|
||||
@@ -3,7 +3,6 @@ defmodule ClaperWeb.AdminLive.EventLive do
|
||||
|
||||
alias Claper.Admin
|
||||
alias Claper.Events.Event
|
||||
alias ClaperWeb.Helpers.CSVExporter
|
||||
|
||||
@impl true
|
||||
def mount(_params, session, socket) do
|
||||
@@ -11,12 +10,7 @@ defmodule ClaperWeb.AdminLive.EventLive do
|
||||
Gettext.put_locale(ClaperWeb.Gettext, locale)
|
||||
end
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, gettext("Events"))
|
||||
|> assign(:events, list_events())
|
||||
|> assign(:search, "")
|
||||
|> assign(:current_sort, %{field: :na, order: :asc})}
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -24,10 +18,25 @@ defmodule ClaperWeb.AdminLive.EventLive do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
defp apply_action(socket, :index, params) do
|
||||
{events, meta} = Admin.list_events_paginated(params)
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Events")
|
||||
|> assign(:page_title, gettext("Events"))
|
||||
|> assign(:event, nil)
|
||||
|> assign(:events, events)
|
||||
|> assign(:meta, meta)
|
||||
|> assign(:form, Phoenix.Component.to_form(meta))
|
||||
|> assign_new(:fields, fn ->
|
||||
[
|
||||
name: [
|
||||
label: nil,
|
||||
placeholder: gettext("Search events..."),
|
||||
type: "text",
|
||||
op: :ilike_or
|
||||
]
|
||||
]
|
||||
end)
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
@@ -56,120 +65,14 @@ defmodule ClaperWeb.AdminLive.EventLive do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Event deleted successfully"))
|
||||
|> assign(:events, list_events())}
|
||||
|> push_patch(to: Flop.Phoenix.build_path(~p"/admin/events", socket.assigns.meta.flop))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("search", %{"search" => search}, socket) do
|
||||
events = search_events(search)
|
||||
{:noreply, socket |> assign(:search, search) |> assign(:events, events)}
|
||||
end
|
||||
def handle_event("filter-events", unsigned_params, socket) do
|
||||
flop = Flop.validate!(unsigned_params, for: Event, replace_invalid_params: true)
|
||||
to = Flop.Phoenix.build_path(~p"/admin/events", flop)
|
||||
|
||||
@impl true
|
||||
def handle_event("sort", %{"field" => field}, socket) do
|
||||
field = String.to_existing_atom(field)
|
||||
current_sort = socket.assigns.current_sort
|
||||
|
||||
direction =
|
||||
if current_sort.field == field && current_sort.order == :asc, do: :desc, else: :asc
|
||||
|
||||
events = sort_events(socket.assigns.events, field, direction)
|
||||
current_sort = %{field: field, order: direction}
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:events, events)
|
||||
|> assign(:current_sort, current_sort)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:export_csv_requested, _params}, socket) do
|
||||
filename = CSVExporter.generate_filename("events")
|
||||
csv_content = CSVExporter.export_events_to_csv(socket.assigns.events)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Events exported successfully"))
|
||||
|> push_event("download_csv", %{filename: filename, content: csv_content})}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:table_action, action, event, _event_id}, socket) do
|
||||
case action do
|
||||
:view ->
|
||||
{:noreply, push_navigate(socket, to: ~p"/admin/events/#{event}")}
|
||||
|
||||
:edit ->
|
||||
{:noreply, push_navigate(socket, to: ~p"/admin/events/#{event}/edit")}
|
||||
|
||||
:delete ->
|
||||
{:ok, _} = Claper.Events.delete_event(event)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Event deleted successfully"))
|
||||
|> assign(:events, list_events())}
|
||||
end
|
||||
end
|
||||
|
||||
defp list_events do
|
||||
Admin.list_all_events()
|
||||
end
|
||||
|
||||
defp search_events(search) when search == "", do: list_events()
|
||||
|
||||
defp search_events(search) do
|
||||
Admin.list_all_events(%{"search" => search})
|
||||
end
|
||||
|
||||
defp sort_events(events, field, order) do
|
||||
Enum.sort_by(events, &Map.get(&1, field), order)
|
||||
end
|
||||
|
||||
def sort_indicator(assigns) do
|
||||
~H"""
|
||||
<%= if @current_sort.field == @field do %>
|
||||
<%= if @current_sort.order == :asc do %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-500 group-hover:text-gray-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-500 group-hover:text-gray-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M14.707 12.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l2.293-2.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-400 opacity-0 group-hover:opacity-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% end %>
|
||||
"""
|
||||
{:noreply, push_patch(socket, to: to)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,249 +1,95 @@
|
||||
<%= case @live_action do %>
|
||||
<% :index -> %>
|
||||
<div phx-hook="CSVDownloader" id="event-list">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold">{gettext("Events")}</h1>
|
||||
<div class="flex space-x-3">
|
||||
<%!-- <.link navigate={~p"/admin/events/new"} class="btn btn-primary btn-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
class="w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("New event")}
|
||||
</.link> --%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mb-6">{gettext("Events")}</h1>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="py-4">
|
||||
<!-- Search Bar -->
|
||||
<div class="mb-6">
|
||||
<form phx-change="search" class="flex w-full md:w-1/2 my-3">
|
||||
<label class="input">
|
||||
<svg
|
||||
class="h-[1em] opacity-50"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.3-4.3"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
value={@search}
|
||||
placeholder={gettext("Search events...")}
|
||||
/>
|
||||
</label>
|
||||
</form>
|
||||
<.form
|
||||
for={@form}
|
||||
class="flex flex-col sm:flex-row gap-3 sm:[&>div]:basis-[250px]"
|
||||
phx-change="filter-events"
|
||||
>
|
||||
<Flop.Phoenix.filter_fields :let={i} form={@form} fields={@fields}>
|
||||
<ClaperWeb.CoreComponents.input
|
||||
field={i.field}
|
||||
label={i.label}
|
||||
type={i.type}
|
||||
phx-debounce="300"
|
||||
{i.rest}
|
||||
/>
|
||||
</Flop.Phoenix.filter_fields>
|
||||
</.form>
|
||||
|
||||
<Flop.Phoenix.table
|
||||
items={@events}
|
||||
meta={@meta}
|
||||
path={~p"/admin/events"}
|
||||
opts={[
|
||||
container: true,
|
||||
container_attrs: [class: "card bg-base-100 shadow-xl overflow-x-auto my-5"],
|
||||
no_results_content: ~H"""
|
||||
<p class="text-center py-10">{gettext("No events found")}</p>
|
||||
""",
|
||||
table_attrs: [class: "table table-zebra w-full"],
|
||||
symbol_asc: ~H"""
|
||||
<span aria-hidden="true"><ClaperWeb.Icons.arrow_up /></span>
|
||||
""",
|
||||
symbol_desc: ~H"""
|
||||
<span aria-hidden="true"><ClaperWeb.Icons.arrow_down /></span>
|
||||
"""
|
||||
]}
|
||||
>
|
||||
<:col :let={event} label={gettext("Name")} field={:name}>{event.name}</:col>
|
||||
<:col :let={event} label={gettext("Code")} field={:code}>
|
||||
<span class="uppercase">{event.code}</span>
|
||||
</:col>
|
||||
<:col :let={event} label={gettext("Owner")}>
|
||||
{if event.user, do: event.user.email, else: gettext("No owner")}
|
||||
</:col>
|
||||
<:col :let={event} label={gettext("Started At")} field={:started_at}>
|
||||
{if event.started_at,
|
||||
do: Calendar.strftime(event.started_at, "%Y-%m-%d %H:%M"),
|
||||
else: gettext("Not started")}
|
||||
</:col>
|
||||
<:col :let={event} label={gettext("Expired At")} field={:expired_at}>
|
||||
{if event.expired_at,
|
||||
do: Calendar.strftime(event.expired_at, "%Y-%m-%d %H:%M"),
|
||||
else: gettext("Not expired")}
|
||||
</:col>
|
||||
<:col :let={event} label={gettext("Audience Peak")} field={:audience_peak}>
|
||||
<div class="badge badge-outline">{event.audience_peak || 0}</div>
|
||||
</:col>
|
||||
<:action :let={event} tbody_td_attrs={[class: "text-right"]}>
|
||||
<div class="flex justify-end gap-2">
|
||||
<.link
|
||||
navigate={~p"/admin/events/#{event}"}
|
||||
class="btn btn-link btn-sm"
|
||||
title={gettext("View event")}
|
||||
>
|
||||
<ClaperWeb.Icons.eye />
|
||||
</.link>
|
||||
<.link
|
||||
navigate={~p"/admin/events/#{event}/edit"}
|
||||
class="btn btn-link btn-sm text-primary"
|
||||
title={gettext("Edit event")}
|
||||
>
|
||||
<span class="hero-pencil-square w-5 h-5" />
|
||||
</.link>
|
||||
<a
|
||||
href="#"
|
||||
phx-click="delete"
|
||||
phx-value-id={event.id}
|
||||
data-confirm={gettext("Are you sure you want to delete this event?")}
|
||||
class="btn btn-link btn-sm text-error"
|
||||
title={gettext("Delete event")}
|
||||
>
|
||||
<span class="hero-trash w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- events Table -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="name"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Name")} {sort_indicator(assigns |> Map.put(:field, :name))}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="code"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Code")} {sort_indicator(assigns |> Map.put(:field, :code))}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
{gettext("Owner")}
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="started_at"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Started At")} {sort_indicator(
|
||||
assigns
|
||||
|> Map.put(:field, :started_at)
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="expired_at"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Expired At")} {sort_indicator(
|
||||
assigns
|
||||
|> Map.put(:field, :expired_at)
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="audience_peak"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Audience Peak")} {sort_indicator(
|
||||
assigns
|
||||
|> Map.put(:field, :audience_peak)
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="sr-only">{gettext("Actions")}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= if Enum.empty?(@events) do %>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">
|
||||
{gettext("No events found")}
|
||||
</td>
|
||||
</tr>
|
||||
<% else %>
|
||||
<%= for event <- @events do %>
|
||||
<tr id={"event-#{event.id}"}>
|
||||
<td class="font-medium">
|
||||
{event.name}
|
||||
</td>
|
||||
<td class="uppercase">
|
||||
{event.code}
|
||||
</td>
|
||||
<td>
|
||||
{if event.user, do: event.user.email, else: gettext("No owner")}
|
||||
</td>
|
||||
<td>
|
||||
{if event.started_at,
|
||||
do: Calendar.strftime(event.started_at, "%Y-%m-%d %H:%M"),
|
||||
else: gettext("Not started")}
|
||||
</td>
|
||||
<td>
|
||||
{if event.expired_at,
|
||||
do: Calendar.strftime(event.expired_at, "%Y-%m-%d %H:%M"),
|
||||
else: gettext("Not expired")}
|
||||
</td>
|
||||
<td>
|
||||
<div class="badge badge-outline">
|
||||
{event.audience_peak || 0}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<.link
|
||||
navigate={~p"/admin/events/#{event}"}
|
||||
class="btn btn-link btn-sm"
|
||||
title={gettext("View event")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</.link>
|
||||
<.link
|
||||
navigate={~p"/admin/events/#{event}/edit"}
|
||||
class="btn btn-link btn-sm text-primary"
|
||||
title={gettext("Edit event")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<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>
|
||||
</.link>
|
||||
<a
|
||||
href="#"
|
||||
phx-click="delete"
|
||||
phx-value-id={event.id}
|
||||
data-confirm={
|
||||
gettext("Are you sure you want to delete this event?")
|
||||
}
|
||||
class="btn btn-link btn-sm text-error"
|
||||
title={gettext("Delete event")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</:action>
|
||||
</Flop.Phoenix.table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<ClaperWeb.CoreComponents.pagination meta={@meta} path={~p"/admin/events"} />
|
||||
</div>
|
||||
</div>
|
||||
<% :show -> %>
|
||||
|
||||
@@ -4,7 +4,6 @@ defmodule ClaperWeb.AdminLive.UserLive do
|
||||
alias Claper.Admin
|
||||
alias Claper.Accounts
|
||||
alias Claper.Accounts.User
|
||||
alias ClaperWeb.Helpers.CSVExporter
|
||||
|
||||
@impl true
|
||||
def mount(_params, session, socket) do
|
||||
@@ -12,12 +11,7 @@ defmodule ClaperWeb.AdminLive.UserLive do
|
||||
Gettext.put_locale(ClaperWeb.Gettext, locale)
|
||||
end
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, gettext("Users"))
|
||||
|> assign(:users, list_users())
|
||||
|> assign(:search, "")
|
||||
|> assign(:current_sort, %{field: :na, order: :asc})}
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -25,10 +19,25 @@ defmodule ClaperWeb.AdminLive.UserLive do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
defp apply_action(socket, :index, params) do
|
||||
{users, meta} = Admin.list_users_paginated(params)
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Users")
|
||||
|> assign(:page_title, gettext("Users"))
|
||||
|> assign(:user, nil)
|
||||
|> assign(:users, users)
|
||||
|> assign(:meta, meta)
|
||||
|> assign(:form, Phoenix.Component.to_form(meta))
|
||||
|> assign_new(:fields, fn ->
|
||||
[
|
||||
email: [
|
||||
label: nil,
|
||||
placeholder: gettext("Search users..."),
|
||||
type: "text",
|
||||
op: :ilike_or
|
||||
]
|
||||
]
|
||||
end)
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
@@ -57,142 +66,14 @@ defmodule ClaperWeb.AdminLive.UserLive do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("User deleted successfully"))
|
||||
|> assign(:users, list_users())}
|
||||
|> push_patch(to: Flop.Phoenix.build_path(~p"/admin/users", socket.assigns.meta.flop))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("search", %{"search" => search}, socket) do
|
||||
users = search_users(search)
|
||||
{:noreply, socket |> assign(:search, search) |> assign(:users, users)}
|
||||
end
|
||||
def handle_event("filter-users", unsigned_params, socket) do
|
||||
flop = Flop.validate!(unsigned_params, for: User, replace_invalid_params: true)
|
||||
to = Flop.Phoenix.build_path(~p"/admin/users", flop)
|
||||
|
||||
@impl true
|
||||
def handle_event("sort", %{"field" => field}, socket) do
|
||||
field_atom = String.to_existing_atom(field)
|
||||
current_sort = socket.assigns.current_sort
|
||||
|
||||
new_direction =
|
||||
if current_sort.field == field_atom do
|
||||
if current_sort.order == :asc, do: :desc, else: :asc
|
||||
else
|
||||
:asc
|
||||
end
|
||||
|
||||
users = sort_users(socket.assigns.users, field_atom, new_direction)
|
||||
current_sort = %{field: field_atom, order: new_direction}
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:users, users)
|
||||
|> assign(:current_sort, current_sort)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:search_filter_changed, %{search: search, filters: _filters}}, socket) do
|
||||
users = search_users(search)
|
||||
{:noreply, socket |> assign(:search, search) |> assign(:users, users)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:export_csv_requested, _params}, socket) do
|
||||
filename = CSVExporter.generate_filename("users")
|
||||
csv_content = CSVExporter.export_users_to_csv(socket.assigns.users)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Users exported successfully"))
|
||||
|> push_event("download_csv", %{filename: filename, content: csv_content})}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:table_sort_changed, sort_config}, socket) do
|
||||
%{field: field, direction: direction} = sort_config
|
||||
users = sort_users(socket.assigns.users, field, direction)
|
||||
current_sort = %{field: field, order: direction}
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:users, users)
|
||||
|> assign(:current_sort, current_sort)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:table_action, action, user, _user_id}, socket) do
|
||||
case action do
|
||||
:view ->
|
||||
{:noreply, push_navigate(socket, to: ~p"/admin/users/#{user}")}
|
||||
|
||||
:edit ->
|
||||
{:noreply, push_navigate(socket, to: ~p"/admin/users/#{user}/edit")}
|
||||
|
||||
:delete ->
|
||||
{:ok, _} = Accounts.delete_user(user)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("User deleted successfully"))
|
||||
|> assign(:users, list_users())}
|
||||
end
|
||||
end
|
||||
|
||||
def list_users do
|
||||
Admin.list_all_users()
|
||||
end
|
||||
|
||||
defp search_users(search) when search == "", do: list_users()
|
||||
|
||||
defp search_users(search) do
|
||||
Admin.list_all_users(%{"search" => search})
|
||||
end
|
||||
|
||||
defp sort_users(users, field, order) do
|
||||
Enum.sort_by(users, &Map.get(&1, field), order)
|
||||
end
|
||||
|
||||
def sort_indicator(assigns) do
|
||||
~H"""
|
||||
<%= if @current_sort.field == @field do %>
|
||||
<%= if @current_sort.order == :asc do %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-500 group-hover:text-gray-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-500 group-hover:text-gray-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M14.707 12.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l2.293-2.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 text-gray-400 opacity-0 group-hover:opacity-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<% end %>
|
||||
"""
|
||||
{:noreply, push_patch(socket, to: to)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,230 +1,89 @@
|
||||
<%= case @live_action do %>
|
||||
<% :index -> %>
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold">{gettext("Users")}</h1>
|
||||
<div class="flex space-x-3">
|
||||
<%!-- <.link navigate={~p"/admin/users/new"} class="btn btn-primary btn-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
class="w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("New user")}
|
||||
</.link> --%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mb-6">{gettext("Users")}</h1>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="py-4">
|
||||
<!-- Search Bar -->
|
||||
<div class="mb-6">
|
||||
<form phx-change="search" class="flex w-full md:w-1/2 my-3">
|
||||
<label class="input">
|
||||
<svg
|
||||
class="h-[1em] opacity-50"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.3-4.3"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
value={@search}
|
||||
placeholder={gettext("Search users...")}
|
||||
/>
|
||||
</label>
|
||||
</form>
|
||||
<.form
|
||||
for={@form}
|
||||
class="flex flex-col sm:flex-row gap-3 sm:[&>div]:basis-[250px]"
|
||||
phx-change="filter-users"
|
||||
>
|
||||
<Flop.Phoenix.filter_fields :let={i} form={@form} fields={@fields}>
|
||||
<ClaperWeb.CoreComponents.input
|
||||
field={i.field}
|
||||
label={i.label}
|
||||
type={i.type}
|
||||
phx-debounce="300"
|
||||
{i.rest}
|
||||
/>
|
||||
</Flop.Phoenix.filter_fields>
|
||||
</.form>
|
||||
|
||||
<Flop.Phoenix.table
|
||||
items={@users}
|
||||
meta={@meta}
|
||||
path={~p"/admin/users"}
|
||||
opts={[
|
||||
container: true,
|
||||
container_attrs: [class: "card bg-base-100 shadow-xl overflow-x-auto my-5"],
|
||||
no_results_content: ~H"""
|
||||
<p class="text-center py-10">{gettext("No users found")}</p>
|
||||
""",
|
||||
table_attrs: [class: "table table-zebra w-full"],
|
||||
symbol_asc: ~H"""
|
||||
<span aria-hidden="true"><ClaperWeb.Icons.arrow_up /></span>
|
||||
""",
|
||||
symbol_desc: ~H"""
|
||||
<span aria-hidden="true"><ClaperWeb.Icons.arrow_down /></span>
|
||||
"""
|
||||
]}
|
||||
>
|
||||
<:col :let={user} label={gettext("Email")} field={:email}>{user.email}</:col>
|
||||
<:col :let={user} label={gettext("Role")} field={:role_id}>
|
||||
{if user.role, do: user.role.name, else: gettext("No role")}
|
||||
</:col>
|
||||
<:col :let={user} label={gettext("Status")} field={:confirmed_at}>
|
||||
<%= if user.confirmed_at do %>
|
||||
<span class="badge badge-success">{gettext("Confirmed")}</span>
|
||||
<% else %>
|
||||
<span class="badge badge-warning">{gettext("Unconfirmed")}</span>
|
||||
<% end %>
|
||||
</:col>
|
||||
<:col :let={user} label={gettext("Created")} field={:inserted_at}>
|
||||
{Calendar.strftime(user.inserted_at, "%Y-%m-%d %H:%M")}
|
||||
</:col>
|
||||
<:action :let={user} tbody_td_attrs={[class: "text-right"]}>
|
||||
<div class="flex justify-end gap-2">
|
||||
<.link
|
||||
navigate={~p"/admin/users/#{user}"}
|
||||
class="btn btn-link btn-sm"
|
||||
title={gettext("View user")}
|
||||
>
|
||||
<ClaperWeb.Icons.eye />
|
||||
</.link>
|
||||
<.link
|
||||
navigate={~p"/admin/users/#{user}/edit"}
|
||||
class="btn btn-link btn-sm text-primary"
|
||||
title={gettext("Edit user")}
|
||||
>
|
||||
<span class="hero-pencil-square w-5 h-5" />
|
||||
</.link>
|
||||
<a
|
||||
href="#"
|
||||
phx-click="delete"
|
||||
phx-value-id={user.id}
|
||||
data-confirm={gettext("Are you sure you want to delete this user?")}
|
||||
class="btn btn-link btn-sm text-error"
|
||||
title={gettext("Delete user")}
|
||||
>
|
||||
<span class="hero-trash w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="email"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Email")} {sort_indicator(assigns |> Map.put(:field, :email))}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="role_id"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Role")} {sort_indicator(assigns |> Map.put(:field, :role_id))}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="confirmed_at"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Status")} {sort_indicator(
|
||||
assigns
|
||||
|> Map.put(:field, :confirmed_at)
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-left">
|
||||
<button
|
||||
phx-click="sort"
|
||||
phx-value-field="inserted_at"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
{gettext("Created")} {sort_indicator(
|
||||
assigns
|
||||
|> Map.put(:field, :inserted_at)
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="sr-only">{gettext("Actions")}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= if Enum.empty?(@users) do %>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">
|
||||
{gettext("No users found")}
|
||||
</td>
|
||||
</tr>
|
||||
<% else %>
|
||||
<%= for user <- @users do %>
|
||||
<tr id={"user-#{user.id}"}>
|
||||
<td class="font-medium">
|
||||
{user.email}
|
||||
</td>
|
||||
<td>
|
||||
{if user.role, do: user.role.name, else: gettext("No role")}
|
||||
</td>
|
||||
<td>
|
||||
<%= if user.confirmed_at do %>
|
||||
<span class="badge badge-success">
|
||||
{gettext("Confirmed")}
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="badge badge-warning">
|
||||
{gettext("Unconfirmed")}
|
||||
</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
{Calendar.strftime(user.inserted_at, "%Y-%m-%d %H:%M")}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<.link
|
||||
navigate={~p"/admin/users/#{user}"}
|
||||
class="btn btn-link btn-sm"
|
||||
title={gettext("View user")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</.link>
|
||||
<.link
|
||||
navigate={~p"/admin/users/#{user}/edit"}
|
||||
class="btn btn-link btn-sm text-primary"
|
||||
title={gettext("Edit user")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<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>
|
||||
</.link>
|
||||
<a
|
||||
href="#"
|
||||
phx-click="delete"
|
||||
phx-value-id={user.id}
|
||||
data-confirm={
|
||||
gettext("Are you sure you want to delete this user?")
|
||||
}
|
||||
class="btn btn-link btn-sm text-error"
|
||||
title={gettext("Delete user")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</:action>
|
||||
</Flop.Phoenix.table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<ClaperWeb.CoreComponents.pagination meta={@meta} path={~p"/admin/users"} />
|
||||
</div>
|
||||
</div>
|
||||
<% :show -> %>
|
||||
|
||||
@@ -22,10 +22,14 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
Presentations.get_first_slide_url(event.presentation_file)
|
||||
end
|
||||
|
||||
defp dom_id(component_id, suffix) do
|
||||
"#{component_id}-#{suffix}"
|
||||
end
|
||||
|
||||
defp render_grid_card(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
id={"event-#{@event.uuid}"}
|
||||
id={dom_id(@id, "card")}
|
||||
class="group relative bg-white border border-gray-200 rounded-2xl overflow-hidden hover:shadow-lg transition-shadow duration-200 h-96"
|
||||
x-data="{showActions: false, showJoinMenu: false}"
|
||||
@mouseenter="showActions = true"
|
||||
@@ -49,7 +53,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<span class="text-sm font-medium text-gray-600">{gettext("Processing...")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Status Badge -->
|
||||
<div class="absolute top-4 left-4 z-10">
|
||||
<%= if Event.started?(@event) && !Event.finished?(@event) do %>
|
||||
@@ -69,7 +73,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- LTI Badge -->
|
||||
<div :if={@event.lti_resource} class="absolute top-4 right-4 z-10">
|
||||
<span class="badge badge-neutral gap-1">
|
||||
@@ -88,7 +92,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
LTI
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Sliding Bottom Panel -->
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 bg-white transition-transform duration-300 ease-out z-20"
|
||||
@@ -105,12 +109,12 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
# {@event.code}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 3-dot Menu -->
|
||||
<div :if={not @is_leader} class="relative shrink-0">
|
||||
<button
|
||||
phx-click-away={JS.hide(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click={JS.toggle(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click-away={JS.hide(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-click={JS.toggle(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-target={@myself}
|
||||
class="p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded"
|
||||
>
|
||||
@@ -126,7 +130,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id={"dropdown-menu-#{@event.uuid}"}
|
||||
id={dom_id(@id, "dropdown-menu")}
|
||||
phx-hook="Dropdown"
|
||||
class="hidden absolute right-0 bottom-full mb-2 w-52 dropdown-content menu bg-base-100 rounded-box shadow-lg z-30"
|
||||
>
|
||||
@@ -135,7 +139,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Action Buttons (revealed on hover) -->
|
||||
<div
|
||||
:if={@event.presentation_file.status == "done" && !Event.finished?(@event)}
|
||||
@@ -247,14 +251,14 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
{gettext("End Event")}
|
||||
</.link>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Error Status -->
|
||||
<div :if={@event.presentation_file.status == "fail"} class="px-2 pb-2">
|
||||
<span class="text-sm text-supporting-red-500">
|
||||
{gettext("Error when processing the file")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Finished Event Actions -->
|
||||
<div :if={Event.finished?(@event)} class="px-2 pb-2">
|
||||
<a href={~p"/events/#{@event.uuid}/stats"} class="btn btn-primary w-full">
|
||||
@@ -277,7 +281,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
|
||||
defp render_list_card(assigns) do
|
||||
~H"""
|
||||
<div class="w-full" id={"event-#{@event.uuid}"} x-data="{showJoinMenu: false}">
|
||||
<div class="w-full" id={dom_id(@id, "card")} x-data="{showJoinMenu: false}">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 hover:shadow-lg transition-shadow duration-200">
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<!-- Thumbnail -->
|
||||
@@ -297,7 +301,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<img src="/images/logo.svg" class="h-6 animate-pulse" alt="Loading" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Event Info -->
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -341,19 +345,19 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<span class="font-medium uppercase"># {@event.code}</span>
|
||||
<span
|
||||
:if={!Event.finished?(@event) && !Event.started?(@event)}
|
||||
id={"event-date-#{@event.uuid}"}
|
||||
id={dom_id(@id, "event-date")}
|
||||
phx-update="ignore"
|
||||
>
|
||||
{gettext("Starting on")}
|
||||
<span x-text={"moment.utc('#{@event.started_at}').local().format('lll')"}></span>
|
||||
</span>
|
||||
<span :if={Event.finished?(@event)} id={"event-date-#{@event.uuid}"} phx-update="ignore">
|
||||
<span :if={Event.finished?(@event)} id={dom_id(@id, "event-date")} phx-update="ignore">
|
||||
{gettext("Finished on")}
|
||||
<span x-text={"moment.utc('#{@event.expired_at}').local().format('lll')"}></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-2 ml-auto">
|
||||
<%= if @event.presentation_file.status == "done" && !Event.finished?(@event) do %>
|
||||
@@ -491,12 +495,12 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
{gettext("View report")}
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
|
||||
<!-- 3-dot Menu -->
|
||||
<div :if={not @is_leader} class="relative">
|
||||
<button
|
||||
phx-click-away={JS.hide(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click={JS.toggle(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click-away={JS.hide(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-click={JS.toggle(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-target={@myself}
|
||||
class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded"
|
||||
>
|
||||
@@ -512,7 +516,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id={"dropdown-menu-#{@event.uuid}"}
|
||||
id={dom_id(@id, "dropdown-menu")}
|
||||
phx-hook="Dropdown"
|
||||
class="hidden absolute right-0 top-10 w-52 dropdown-content menu bg-base-100 rounded-box shadow-lg z-30"
|
||||
>
|
||||
@@ -528,7 +532,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
|
||||
defp render_mobile_card(assigns) do
|
||||
~H"""
|
||||
<div class="w-full" id={"event-#{@event.uuid}"} x-data="{showJoinMenu: false}">
|
||||
<div class="w-full" id={dom_id(@id, "card")} x-data="{showJoinMenu: false}">
|
||||
<div class="bg-white rounded-3xl border border-gray-200 p-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Top Row: Thumbnail + Info + Menu -->
|
||||
@@ -550,7 +554,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<img src="/images/logo.svg" class="h-8 animate-pulse" alt="Loading" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Event Info -->
|
||||
<div class="flex-1 min-w-0 py-1">
|
||||
<h3 class="font-semibold text-gray-800 text-lg leading-tight truncate">
|
||||
@@ -577,12 +581,12 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 3-dot Menu -->
|
||||
<div :if={not @is_leader} class="relative shrink-0">
|
||||
<button
|
||||
phx-click-away={JS.hide(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click={JS.toggle(to: "#dropdown-menu-#{@event.uuid}")}
|
||||
phx-click-away={JS.hide(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-click={JS.toggle(to: "##{dom_id(@id, "dropdown-menu")}")}
|
||||
phx-target={@myself}
|
||||
class="p-1.5 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
@@ -598,7 +602,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id={"dropdown-menu-#{@event.uuid}"}
|
||||
id={dom_id(@id, "dropdown-menu")}
|
||||
phx-hook="Dropdown"
|
||||
class="hidden absolute right-0 top-8 w-52 dropdown-content menu bg-base-100 rounded-box shadow-lg z-30"
|
||||
>
|
||||
@@ -606,7 +610,7 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bottom Row: Action Buttons -->
|
||||
<div class="flex gap-2">
|
||||
<%= if @event.presentation_file.status == "done" && !Event.finished?(@event) do %>
|
||||
@@ -756,8 +760,16 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<ul class="w-full">
|
||||
<%= if !Event.finished?(@event) && not @is_leader do %>
|
||||
<li>
|
||||
<.link patch={~p"/events/#{@event.uuid}/edit"} class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<.link
|
||||
patch={~p"/events/#{@event.uuid}/edit"}
|
||||
class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path d="m5.433 13.917 1.262-3.155A4 4 0 0 1 7.58 9.42l6.92-6.918a2.121 2.121 0 0 1 3 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 0 1-.65-.65Z" />
|
||||
<path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0 0 10 3H4.75A2.75 2.75 0 0 0 2 5.75v9.5A2.75 2.75 0 0 0 4.75 18h9.5A2.75 2.75 0 0 0 17 15.25V10a.75.75 0 0 0-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5Z" />
|
||||
</svg>
|
||||
@@ -765,8 +777,17 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<button phx-value-id={@event.uuid} phx-click="duplicate" class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<button
|
||||
phx-value-id={@event.uuid}
|
||||
phx-click="duplicate"
|
||||
class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path d="M7 3.5A1.5 1.5 0 0 1 8.5 2h3.879a1.5 1.5 0 0 1 1.06.44l3.122 3.12A1.5 1.5 0 0 1 17 6.622V12.5a1.5 1.5 0 0 1-1.5 1.5h-1v-3.379a3 3 0 0 0-.879-2.121L10.5 5.379A3 3 0 0 0 8.379 4.5H7v-1Z" />
|
||||
<path d="M4.5 6A1.5 1.5 0 0 0 3 7.5v9A1.5 1.5 0 0 0 4.5 18h7a1.5 1.5 0 0 0 1.5-1.5v-5.879a1.5 1.5 0 0 0-.44-1.06L9.44 6.439A1.5 1.5 0 0 0 8.378 6H4.5Z" />
|
||||
</svg>
|
||||
@@ -777,8 +798,17 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
|
||||
<%= if Event.finished?(@event) && not @is_leader do %>
|
||||
<li>
|
||||
<button phx-value-id={@event.uuid} phx-click="duplicate" class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<button
|
||||
phx-value-id={@event.uuid}
|
||||
phx-click="duplicate"
|
||||
class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path d="M7 3.5A1.5 1.5 0 0 1 8.5 2h3.879a1.5 1.5 0 0 1 1.06.44l3.122 3.12A1.5 1.5 0 0 1 17 6.622V12.5a1.5 1.5 0 0 1-1.5 1.5h-1v-3.379a3 3 0 0 0-.879-2.121L10.5 5.379A3 3 0 0 0 8.379 4.5H7v-1Z" />
|
||||
<path d="M4.5 6A1.5 1.5 0 0 0 3 7.5v9A1.5 1.5 0 0 0 4.5 18h7a1.5 1.5 0 0 0 1.5-1.5v-5.879a1.5 1.5 0 0 0-.44-1.06L9.44 6.439A1.5 1.5 0 0 0 8.378 6H4.5Z" />
|
||||
</svg>
|
||||
@@ -796,8 +826,17 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
}
|
||||
class="flex items-center gap-3 w-full px-3 py-2 hover:bg-gray-50 text-error"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z" clip-rule="evenodd" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("Delete")}
|
||||
</.link>
|
||||
|
||||
@@ -191,7 +191,7 @@ defmodule ClaperWeb.EventLive.Index do
|
||||
defp load_events(socket) do
|
||||
params = %{
|
||||
"page" => socket.assigns.page,
|
||||
"page_size" => 5,
|
||||
"page_size" => 6,
|
||||
"search" => socket.assigns.search_query
|
||||
}
|
||||
|
||||
|
||||
@@ -358,11 +358,11 @@
|
||||
/>
|
||||
<path d="M5.082 14.254a8.287 8.287 0 0 0-1.308 5.135 9.687 9.687 0 0 1-1.764-.44l-.115-.04a.563.563 0 0 1-.373-.487l-.01-.121a3.75 3.75 0 0 1 3.57-4.047ZM20.226 19.389a8.287 8.287 0 0 0-1.308-5.135 3.75 3.75 0 0 1 3.57 4.047l-.01.121a.563.563 0 0 1-.373.486l-.115.04c-.567.2-1.156.349-1.764.441Z" />
|
||||
</svg>
|
||||
<span class="text-sm font-bold text-gray-700">{gettext("Join")}</span>
|
||||
<span id="attendees-count" phx-update="ignore" phx-hook="UpdateAttendees" class="text-sm font-bold text-gray-700">
|
||||
<span class="text-gray-700">{gettext("Join")}</span>
|
||||
<span id="attendees-count" phx-update="ignore" phx-hook="UpdateAttendees" class="text-gray-700">
|
||||
{@attendees_nb}
|
||||
</span>
|
||||
<span class="text-sm font-bold text-gray-700">{gettext("attendees")}</span>
|
||||
<span class="text-gray-700">{gettext("attendees")}</span>
|
||||
<!-- Arrow icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -66,18 +66,51 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
|
||||
<div class="grid grid-cols-4 gap-1">
|
||||
<.action_button patch={~p"/e/#{@event_code}/manage/add/poll"} label={gettext("Poll")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</.action_button>
|
||||
<.action_button patch={~p"/e/#{@event_code}/manage/add/form"} label={gettext("Form")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
|
||||
/>
|
||||
</svg>
|
||||
</.action_button>
|
||||
<.action_button patch={~p"/e/#{@event_code}/manage/add/quiz"} label={gettext("Quiz")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
</.action_button>
|
||||
|
||||
@@ -86,8 +119,19 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
@click="open = !open"
|
||||
class="flex flex-col items-center justify-center gap-0.5 py-2 rounded-xl text-gray-600 transition-colors hover:bg-primary-50 hover:text-primary-600 w-full"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-primary-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-bold text-xs leading-normal">{gettext("More")}</span>
|
||||
</button>
|
||||
@@ -108,8 +152,19 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
title={gettext("Poll")}
|
||||
description={gettext("Add a poll to gauge your audience's opinion.")}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</.popup_item>
|
||||
<.popup_item
|
||||
@@ -117,8 +172,19 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
title={gettext("Form")}
|
||||
description={gettext("Create a form to collect feedback from your audience.")}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
|
||||
/>
|
||||
</svg>
|
||||
</.popup_item>
|
||||
<.popup_item
|
||||
@@ -126,8 +192,19 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
title={gettext("Web Content")}
|
||||
description={gettext("Embed external web content into your presentation.")}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z"
|
||||
/>
|
||||
</svg>
|
||||
</.popup_item>
|
||||
<.popup_item
|
||||
@@ -135,8 +212,19 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
title={gettext("Quiz")}
|
||||
description={gettext("Test your audience's knowledge with a quiz.")}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
</.popup_item>
|
||||
</div>
|
||||
@@ -254,7 +342,13 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
patch={edit_path(@event_code, interaction)}
|
||||
class="flex items-center justify-center rounded-full w-[42px] h-[41px] shrink-0 border border-[#140553] text-[#140553]"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M3.33301 17.4998H16.6663M4.72134 10.989C4.36602 11.3451 4.16643 11.8276 4.16634 12.3306V14.9998H6.85217C7.35551 14.9998 7.83801 14.7998 8.19384 14.4431L16.1105 6.52229C16.4657 6.16612 16.6652 5.68364 16.6652 5.18063C16.6652 4.67761 16.4657 4.19513 16.1105 3.83896L15.3288 3.05563C15.1526 2.87925 14.9432 2.73935 14.7129 2.64393C14.4825 2.54851 14.2355 2.49943 13.9862 2.49951C13.7368 2.49959 13.4899 2.54882 13.2596 2.64438C13.0292 2.73995 12.82 2.87998 12.6438 3.05646L4.72134 10.989Z"
|
||||
stroke="#140553"
|
||||
@@ -282,7 +376,14 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
disabled={@page == 0}
|
||||
class="p-1 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -295,7 +396,14 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
disabled={@page >= @total_pages - 1}
|
||||
class="p-1 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -352,7 +460,6 @@ defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
|
||||
~H"""
|
||||
<.link
|
||||
patch={@patch}
|
||||
@click="open = false"
|
||||
class="flex items-center gap-3 p-2 rounded-xl transition-colors hover:bg-primary-50"
|
||||
>
|
||||
<div class="flex items-center justify-center w-9 h-9 rounded-full bg-primary-50 text-primary-500 shrink-0">
|
||||
|
||||
@@ -15,10 +15,10 @@ msgstr ""
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:74
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:254
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:72
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:291
|
||||
#: lib/claper_web/live/admin_live/user_live/form_component.ex:18
|
||||
#: lib/claper_web/live/event_live/manage.ex:842
|
||||
#: lib/claper_web/live/event_live/manage.ex:918
|
||||
#: lib/claper_web/live/form_live/form_component.html.heex:32
|
||||
#: lib/claper_web/live/user_settings_live/show.html.heex:24
|
||||
#: lib/claper_web/templates/user_registration/new.html.heex:29
|
||||
@@ -47,7 +47,7 @@ msgid "Change"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:83
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:81
|
||||
#: lib/claper_web/live/admin_live/event_live/form_component.ex:32
|
||||
#: lib/claper_web/live/event_live/event_form_component.html.heex:276
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -189,10 +189,10 @@ msgstr ""
|
||||
msgid "Created successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:259
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:296
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:240
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:772
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:277
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:764
|
||||
#: lib/claper_web/live/event_live/form_component.ex:97
|
||||
#: lib/claper_web/live/event_live/index.ex:168
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -212,10 +212,10 @@ msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/embed_live/form_component.html.heex:64
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:837
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:802
|
||||
#: lib/claper_web/live/event_live/event_form_component.html.heex:45
|
||||
#: lib/claper_web/live/event_live/event_form_component.html.heex:401
|
||||
#: lib/claper_web/live/event_live/manage_audience_responses_component.ex:252
|
||||
#: lib/claper_web/live/event_live/manage_audience_responses_component.ex:251
|
||||
#: lib/claper_web/live/event_live/manageable_post_component.ex:105
|
||||
#: lib/claper_web/live/event_live/post_component.ex:70
|
||||
#: lib/claper_web/live/event_live/post_component.ex:142
|
||||
@@ -566,7 +566,7 @@ msgstr ""
|
||||
msgid "No messages has been sent"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:819
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:793
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This will delete all data related to your event, this cannot be undone. Confirm ?"
|
||||
msgstr ""
|
||||
@@ -691,13 +691,13 @@ msgid "Form"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:74
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:273
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:72
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:310
|
||||
#: lib/claper_web/live/admin_live/event_live/form_component.ex:19
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24
|
||||
#: lib/claper_web/live/event_live/manage.ex:841
|
||||
#: lib/claper_web/live/event_live/manage.ex:917
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
@@ -728,7 +728,7 @@ msgstr ""
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/manage_audience_responses_component.ex:249
|
||||
#: lib/claper_web/live/event_live/manage_audience_responses_component.ex:248
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This cannot be undone, confirm ?"
|
||||
msgstr ""
|
||||
@@ -1019,6 +1019,9 @@ msgstr ""
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:40
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:276
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:257
|
||||
#: lib/claper_web/live/event_live/manage_interaction_options_component.ex:188
|
||||
#: lib/claper_web/live/event_live/quiz_component.ex:161
|
||||
#: lib/claper_web/live/event_live/quiz_component.ex:209
|
||||
@@ -1112,7 +1115,7 @@ msgid "Questions will appear here."
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/templates/layout/_user_menu.html.heex:13
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:262
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:278
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
@@ -1286,8 +1289,8 @@ msgstr ""
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:790
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:811
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:773
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:785
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
@@ -1337,12 +1340,12 @@ msgstr ""
|
||||
msgid "Try logging in again"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/manage.ex:820
|
||||
#: lib/claper_web/live/event_live/manage.ex:890
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/manage.ex:820
|
||||
#: lib/claper_web/live/event_live/manage.ex:890
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
@@ -1464,6 +1467,9 @@ msgstr ""
|
||||
msgid "New quiz"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:34
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:261
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:242
|
||||
#: lib/claper_web/live/event_live/manage_interaction_options_component.ex:180
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Previous"
|
||||
@@ -1686,9 +1692,10 @@ msgstr ""
|
||||
msgid "Account is confirmed and active"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:126
|
||||
#: lib/claper_web/components/core_components.ex:368
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:124
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:111
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:109
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
@@ -1698,7 +1705,7 @@ msgstr ""
|
||||
msgid "Active Events"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:216
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:214
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Are you sure you want to delete this event?"
|
||||
msgstr ""
|
||||
@@ -1708,7 +1715,7 @@ msgstr ""
|
||||
msgid "Are you sure you want to delete this provider?"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:197
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:195
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Are you sure you want to delete this user?"
|
||||
msgstr ""
|
||||
@@ -1719,8 +1726,8 @@ msgid "Assigned User"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:119
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:299
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:117
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:336
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Audience Peak"
|
||||
msgstr ""
|
||||
@@ -1758,13 +1765,13 @@ msgstr ""
|
||||
msgid "Completed"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:134
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:268
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:132
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:305
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirmed"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:291
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:328
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirmed At"
|
||||
msgstr ""
|
||||
@@ -1785,14 +1792,14 @@ msgid "Create User"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:104
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:102
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:305
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:342
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:278
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:315
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Created At"
|
||||
msgstr ""
|
||||
@@ -1802,7 +1809,7 @@ msgstr ""
|
||||
msgid "Currently running"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:219
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:217
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Delete event"
|
||||
msgstr ""
|
||||
@@ -1812,7 +1819,7 @@ msgstr ""
|
||||
msgid "Delete provider"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:200
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:198
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Delete user"
|
||||
msgstr ""
|
||||
@@ -1823,10 +1830,10 @@ msgid "Edit OIDC Provider"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:41
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:194
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:348
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:358
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:62
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:192
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:385
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:395
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Edit event"
|
||||
msgstr ""
|
||||
@@ -1838,10 +1845,10 @@ msgstr ""
|
||||
msgid "Edit provider"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:42
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:175
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:329
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:339
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:63
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:173
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:366
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:376
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Edit user"
|
||||
msgstr ""
|
||||
@@ -1896,14 +1903,14 @@ msgstr ""
|
||||
msgid "Event created successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:58
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:110
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:79
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:142
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Event deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:47
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:253
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:68
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:290
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Event details"
|
||||
msgstr ""
|
||||
@@ -1913,20 +1920,21 @@ msgstr ""
|
||||
msgid "Event updated successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:16
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:22
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:50
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:6
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:214
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Events"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:92
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:124
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Events exported successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:107
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:291
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:105
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:328
|
||||
#: lib/claper_web/live/admin_live/event_live/form_component.ex:57
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expired At"
|
||||
@@ -1990,22 +1998,22 @@ msgstr ""
|
||||
msgid "Last 30 days"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:311
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:348
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:284
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:321
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Updated"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:35
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:325
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:335
|
||||
#: lib/claper_web/live/admin_live/event_live.ex:56
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:362
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:372
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New event"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:134
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:132
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No events found"
|
||||
msgstr ""
|
||||
@@ -2015,31 +2023,31 @@ msgstr ""
|
||||
msgid "No file chosen"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:147
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:279
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:145
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:316
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No owner"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:129
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:260
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:127
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:297
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No role"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:119
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:117
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No users found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:157
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:295
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:155
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:332
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Not expired"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:152
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:287
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:150
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:324
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Not started"
|
||||
msgstr ""
|
||||
@@ -2071,8 +2079,8 @@ msgid "OIDC scopes to request (defaults to 'openid email profile')"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:87
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:277
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:85
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:314
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
@@ -2113,8 +2121,8 @@ msgstr ""
|
||||
msgid "Response Type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:83
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:258
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:81
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:295
|
||||
#: lib/claper_web/live/admin_live/user_live/form_component.ex:46
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Role"
|
||||
@@ -2138,7 +2146,7 @@ msgstr ""
|
||||
msgid "Scope"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:55
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search events..."
|
||||
msgstr ""
|
||||
@@ -2153,7 +2161,7 @@ msgstr ""
|
||||
msgid "Search providers..."
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:55
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search users..."
|
||||
msgstr ""
|
||||
@@ -2173,8 +2181,8 @@ msgstr ""
|
||||
msgid "Start Date"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:95
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:283
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:93
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:320
|
||||
#: lib/claper_web/live/admin_live/event_live/form_component.ex:46
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Started At"
|
||||
@@ -2183,8 +2191,8 @@ msgstr ""
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92
|
||||
#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:92
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:264
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:90
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:301
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
@@ -2214,8 +2222,8 @@ msgstr ""
|
||||
msgid "Total Users"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:138
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:272
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:136
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:309
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Unconfirmed"
|
||||
msgstr ""
|
||||
@@ -2235,7 +2243,7 @@ msgstr ""
|
||||
msgid "Update User"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:250
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:287
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User Account"
|
||||
msgstr ""
|
||||
@@ -2250,14 +2258,14 @@ msgstr ""
|
||||
msgid "User created successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:59
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:127
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:80
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:164
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:48
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:234
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:69
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:271
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User details"
|
||||
msgstr ""
|
||||
@@ -2277,20 +2285,21 @@ msgstr ""
|
||||
msgid "User's email address (must be unique)"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:17
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:23
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:51
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:6
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:230
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:97
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:135
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Users exported successfully"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:169
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:167
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "View event"
|
||||
msgstr ""
|
||||
@@ -2300,7 +2309,7 @@ msgstr ""
|
||||
msgid "View provider"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:150
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:148
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "View user"
|
||||
msgstr ""
|
||||
@@ -2315,7 +2324,7 @@ msgstr ""
|
||||
msgid "Whether this provider is currently active and available for authentication"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:301
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:338
|
||||
#: lib/claper_web/live/event_live/manage.html.heex:365
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "attendees"
|
||||
@@ -2342,7 +2351,7 @@ msgstr ""
|
||||
msgid "vs last month"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:256
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:293
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Back to events"
|
||||
msgstr ""
|
||||
@@ -2352,7 +2361,7 @@ msgstr ""
|
||||
msgid "Back to providers"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:237
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:274
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Back to users"
|
||||
msgstr ""
|
||||
@@ -2365,9 +2374,9 @@ msgstr ""
|
||||
msgid "New provider"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:36
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:306
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:316
|
||||
#: lib/claper_web/live/admin_live/user_live.ex:57
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:343
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:353
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New user"
|
||||
msgstr ""
|
||||
@@ -2394,7 +2403,7 @@ msgstr ""
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:285
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:301
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Back to app"
|
||||
msgstr ""
|
||||
@@ -2651,3 +2660,116 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New presentation"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:67
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:105
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Action"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.ex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.ex:50
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Audit Log Details"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.ex:24
|
||||
#: lib/claper_web/templates/layout/admin.html.heex:246
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Audit Logs"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:66
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:99
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:78
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:152
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:55
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No results."
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:145
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Resource ID"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:137
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Resource Type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/event_live/manage.ex:898
|
||||
#: lib/claper_web/live/event_live/manage.ex:941
|
||||
#: lib/claper_web/live/event_live/manage.ex:957
|
||||
#: lib/claper_web/live/event_live/manage.ex:999
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Resource not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.ex:33
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search by user email"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:247
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:228
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Showing"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:73
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:130
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Timestamp"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:70
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:111
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:88
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "View details for log #%{id}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/audit_log_live.html.heex:119
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "View profile for %{email}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/components/core_components.ex:74
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:247
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:228
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:247
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:228
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "results"
|
||||
msgstr ""
|
||||
|
||||
#: lib/claper_web/live/admin_live/event_live.html.heex:247
|
||||
#: lib/claper_web/live/admin_live/user_live.html.heex:228
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "to"
|
||||
msgstr ""
|
||||
|
||||
93
test/claper/admin_test.exs
Normal file
93
test/claper/admin_test.exs
Normal file
@@ -0,0 +1,93 @@
|
||||
defmodule Claper.AdminTest do
|
||||
use Claper.DataCase
|
||||
|
||||
alias Claper.Admin
|
||||
|
||||
import Claper.AccountsFixtures
|
||||
import Claper.EventsFixtures
|
||||
|
||||
describe "list_users_paginated/1" do
|
||||
test "returns paginated users" do
|
||||
search = "admin-pagination-user"
|
||||
|
||||
users =
|
||||
for index <- 1..21 do
|
||||
user_fixture(%{email: "#{search}-#{index}@example.com"})
|
||||
end
|
||||
|
||||
{page_1_users, meta_1} =
|
||||
Admin.list_users_paginated(%{
|
||||
"page" => 1,
|
||||
"page_size" => 20,
|
||||
"order_by" => ["email"],
|
||||
"order_directions" => ["asc"],
|
||||
"filters" => [%{"field" => "email", "op" => "ilike_or", "value" => search}]
|
||||
})
|
||||
|
||||
{page_2_users, meta_2} =
|
||||
Admin.list_users_paginated(%{
|
||||
"page" => 2,
|
||||
"page_size" => 20,
|
||||
"order_by" => ["email"],
|
||||
"order_directions" => ["asc"],
|
||||
"filters" => [%{"field" => "email", "op" => "ilike_or", "value" => search}]
|
||||
})
|
||||
|
||||
assert meta_1.total_count == 21
|
||||
assert meta_1.total_pages == 2
|
||||
assert meta_2.total_count == 21
|
||||
assert meta_2.total_pages == 2
|
||||
assert length(page_1_users) == 20
|
||||
assert length(page_2_users) == 1
|
||||
|
||||
expected_user_ids =
|
||||
users
|
||||
|> Enum.sort_by(& &1.email, :asc)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert Enum.map(page_1_users ++ page_2_users, & &1.id) == expected_user_ids
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_events_paginated/1" do
|
||||
test "returns paginated events" do
|
||||
search = "admin-pagination-event"
|
||||
|
||||
events =
|
||||
for index <- 1..21 do
|
||||
event_fixture(%{
|
||||
name: "#{search} #{index}",
|
||||
started_at: NaiveDateTime.add(~N[2026-01-01 00:00:00], index, :second)
|
||||
})
|
||||
end
|
||||
|
||||
{page_1_events, meta_1} =
|
||||
Admin.list_events_paginated(%{
|
||||
"page" => 1,
|
||||
"page_size" => 20,
|
||||
"filters" => [%{"field" => "name", "op" => "ilike_or", "value" => search}]
|
||||
})
|
||||
|
||||
{page_2_events, meta_2} =
|
||||
Admin.list_events_paginated(%{
|
||||
"page" => 2,
|
||||
"page_size" => 20,
|
||||
"filters" => [%{"field" => "name", "op" => "ilike_or", "value" => search}]
|
||||
})
|
||||
|
||||
assert meta_1.total_count == 21
|
||||
assert meta_1.total_pages == 2
|
||||
assert meta_2.total_count == 21
|
||||
assert meta_2.total_pages == 2
|
||||
assert length(page_1_events) == 20
|
||||
assert length(page_2_events) == 1
|
||||
|
||||
expected_event_ids =
|
||||
events
|
||||
|> Enum.sort_by(& &1.started_at, {:desc, NaiveDateTime})
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert Enum.map(page_1_events ++ page_2_events, & &1.id) == expected_event_ids
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user