Packages
phoenix_kit
1.7.131
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/live/activity/index.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
socket={@socket}
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={gettext("Activity")}
current_path={@url_path}
project_title={@project_title}
current_locale={assigns[:current_locale]}
>
<div class="container flex flex-col mx-auto px-4 py-6">
<.admin_page_header
back={Routes.path("/admin")}
title={gettext("Activity")}
subtitle={gettext("Business-level activity across the platform")}
/>
<%!-- Activity List --%>
<.table_default
id="activity-table"
variant="zebra"
size="sm"
class="w-full"
toggleable={true}
items={@entries}
card_title={fn entry -> entry.action end}
card_fields={
fn entry ->
fields = [
%{
label: gettext("Actor"),
value: if(entry.actor, do: entry.actor.email, else: gettext("System"))
}
]
fields =
if entry.resource_type do
fields ++ [%{label: gettext("Subject"), value: entry.resource_type}]
else
fields
end
fields =
if entry.target do
fields ++ [%{label: gettext("Target"), value: entry.target.email}]
else
fields
end
fields =
if entry.metadata["role"] do
fields ++ [%{label: gettext("Role"), value: entry.metadata["role"]}]
else
fields
end
fields ++
[
%{
label: gettext("Date"),
value:
"#{UtilsDate.format_date_with_user_format(entry.inserted_at)} #{UtilsDate.format_time_with_user_format(entry.inserted_at)}"
}
]
end
}
>
<:toolbar_title>
<.form for={%{}} phx-change="filter" class="flex flex-wrap items-center gap-2">
<span class="text-sm text-base-content/70 whitespace-nowrap">
{gettext("Module:")}
</span>
<select name="filter[module]" class="select select-sm w-36 min-w-0">
<option value="">{gettext("All Modules")}</option>
<%= for mod <- @modules do %>
<option value={mod} selected={@filter_module == mod}>
{String.capitalize(mod)}
</option>
<% end %>
</select>
<span class="text-sm text-base-content/70 whitespace-nowrap">
{gettext("Mode:")}
</span>
<select name="filter[mode]" class="select select-sm w-32 min-w-0">
<option value="">{gettext("All Modes")}</option>
<%= for mode <- @modes do %>
<option value={mode} selected={@filter_mode == mode}>
{String.capitalize(mode)}
</option>
<% end %>
</select>
<span class="text-sm text-base-content/70 whitespace-nowrap">
{gettext("Action:")}
</span>
<select name="filter[action]" class="select select-sm w-40 min-w-0">
<option value="">{gettext("All Actions")}</option>
<%= for action <- @action_types do %>
<option value={action} selected={@filter_action == action}>
{action}
</option>
<% end %>
</select>
<span class="text-sm text-base-content/70 whitespace-nowrap">
{gettext("Type:")}
</span>
<select name="filter[resource_type]" class="select select-sm w-36 min-w-0">
<option value="">{gettext("All Types")}</option>
<%= for rt <- @resource_types do %>
<option value={rt} selected={@filter_resource_type == rt}>
{String.capitalize(rt)}
</option>
<% end %>
</select>
</.form>
</:toolbar_title>
<:toolbar_actions>
<span class="text-sm text-base-content/60 whitespace-nowrap">
{ngettext("%{count} activity", "%{count} activities", @total, count: @total)}
</span>
<%= if any_filter_active?(assigns) do %>
<button type="button" phx-click="clear_filters" class="btn btn-sm btn-outline">
<.icon name="hero-x-mark" class="w-4 h-4" /> {gettext("Clear filters")}
</button>
<% end %>
</:toolbar_actions>
<:card_header :let={entry}>
<span class={"badge badge-sm #{action_badge_color(entry.action)}"}>
{entry.action}
</span>
</:card_header>
<:card_actions :let={entry}>
<.link
navigate={Routes.path("/admin/activity/#{entry.uuid}")}
class="btn btn-sm btn-outline btn-info"
>
<.icon name="hero-arrow-top-right-on-square" class="w-4 h-4" /> {gettext("View")}
</.link>
</:card_actions>
<.table_default_header>
<.table_default_row hover={false}>
<.table_default_header_cell>{gettext("Module")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Mode")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Activity")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Actor")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Details")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Subject")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Date")}</.table_default_header_cell>
<.table_default_header_cell class="w-12"></.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= if Enum.empty?(@entries) do %>
<.table_default_row>
<.table_default_cell colspan={8}>
<div class="text-center py-12 text-base-content/60">
<.icon name="hero-bell-slash" class="w-12 h-12 mx-auto mb-2 opacity-50" />
<%= if any_filter_active?(assigns) do %>
<p>{gettext("No activities match the current filters")}</p>
<button
type="button"
phx-click="clear_filters"
class="btn btn-sm btn-outline mt-3"
>
<.icon name="hero-x-mark" class="w-4 h-4" /> {gettext("Clear filters")}
</button>
<% else %>
<p>{gettext("No activities recorded yet")}</p>
<% end %>
</div>
</.table_default_cell>
</.table_default_row>
<% end %>
<%= for entry <- @entries do %>
<.table_default_row>
<.table_default_cell class="text-sm">
<%= if entry.module do %>
<span class="badge badge-outline badge-xs">{entry.module}</span>
<% else %>
<span class="text-base-content/30">—</span>
<% end %>
</.table_default_cell>
<.table_default_cell class="text-sm">
<%= if entry.mode do %>
<span class={"badge badge-xs #{mode_badge_color(entry.mode)}"}>
{entry.mode}
</span>
<% else %>
<span class="text-base-content/30">—</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<span class={"badge badge-sm #{action_badge_color(entry.action)}"}>
{entry.action}
</span>
</.table_default_cell>
<.table_default_cell class="text-sm">
<%= if entry.actor do %>
<span class="font-semibold">{entry.actor.email}</span>
<%= if entry.metadata["actor_role"] do %>
<span class={[
"badge badge-xs ml-1",
if(entry.metadata["actor_role"] == "admin",
do: "badge-warning",
else: "badge-ghost"
)
]}>
{entry.metadata["actor_role"]}
</span>
<% end %>
<% else %>
<span class="text-base-content/50">{gettext("System")}</span>
<% end %>
</.table_default_cell>
<.table_default_cell class="text-xs text-base-content/60">
<%= if entry.target && entry.target_uuid != entry.resource_uuid do %>
<div class="mb-0.5">
<span class="text-base-content/50">→</span>
<span>{entry.target.email}</span>
</div>
<% end %>
<% summary = summarize_details(entry.metadata) %>
<%= if summary do %>
<span>{summary}</span>
<% else %>
<span class="text-base-content/30">—</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<%= if entry.resource_type do %>
<span class="badge badge-ghost badge-xs">{entry.resource_type}</span>
<%= case Map.get(@resource_users, entry.resource_uuid) do %>
<% %{email: email} -> %>
<span class="text-xs ml-1">{email}</span>
<% _ -> %>
<%= if entry.resource_uuid do %>
<span class="text-xs text-base-content/40 ml-1">
{String.slice(to_string(entry.resource_uuid), 0..7)}
</span>
<% end %>
<% end %>
<% else %>
<span class="text-base-content/30">—</span>
<% end %>
</.table_default_cell>
<.table_default_cell class="text-xs text-base-content/60">
<div>{UtilsDate.format_date_with_user_format(entry.inserted_at)}</div>
<div class="text-base-content/40">
{UtilsDate.format_time_with_user_format(entry.inserted_at)}
</div>
</.table_default_cell>
<.table_default_cell>
<.link
navigate={Routes.path("/admin/activity/#{entry.uuid}")}
class="btn btn-ghost btn-xs btn-square"
title={gettext("View details")}
>
<.icon name="hero-arrow-top-right-on-square" class="w-4 h-4" />
</.link>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
<%!-- Pagination --%>
<%= if @total_pages > 1 do %>
<div class="flex justify-center mt-6">
<div class="join">
<%= for page <- max(1, @page - 2)..min(@total_pages, @page + 2) do %>
<.link
patch={
Routes.path(
"/admin/activity?#{URI.encode_query(Enum.reject([{"page", page}, {"module", @filter_module}, {"mode", @filter_mode}, {"action", @filter_action}, {"resource_type", @filter_resource_type}], fn {_k, v} -> is_nil(v) or v == "" end))}"
)
}
class={["join-item btn btn-sm", if(page == @page, do: "btn-active", else: "")]}
>
{page}
</.link>
<% end %>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>