Packages
phoenix_kit
1.7.103
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
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="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="Activity"
subtitle="Business-level activity across the platform"
/>
<%!-- Filters --%>
<div class="bg-base-200 rounded-lg p-4 mb-6">
<.form for={%{}} phx-change="filter" class="flex flex-wrap gap-4 items-end">
<div class="form-control">
<label class="label"><span class="label-text">Module</span></label>
<select name="filter[module]" class="select select-bordered select-sm">
<option value="">All Modules</option>
<%= for mod <- @modules do %>
<option value={mod} selected={@filter_module == mod}>
{String.capitalize(mod)}
</option>
<% end %>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text">Mode</span></label>
<select name="filter[mode]" class="select select-bordered select-sm">
<option value="">All Modes</option>
<%= for mode <- @modes do %>
<option value={mode} selected={@filter_mode == mode}>
{String.capitalize(mode)}
</option>
<% end %>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text">Action</span></label>
<select name="filter[action]" class="select select-bordered select-sm">
<option value="">All Actions</option>
<%= for action <- @action_types do %>
<option value={action} selected={@filter_action == action}>
{action}
</option>
<% end %>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text">Resource Type</span></label>
<select name="filter[resource_type]" class="select select-bordered select-sm">
<option value="">All Types</option>
<%= for rt <- @resource_types do %>
<option value={rt} selected={@filter_resource_type == rt}>
{String.capitalize(rt)}
</option>
<% end %>
</select>
</div>
</.form>
</div>
<%!-- Activity List --%>
<%= if Enum.empty?(@entries) do %>
<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" />
<p>No activities recorded yet</p>
</div>
<% else %>
<.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: "Actor",
value: if(entry.actor, do: entry.actor.email, else: "System")
}
]
fields =
if entry.resource_type do
fields ++ [%{label: "Subject", value: entry.resource_type}]
else
fields
end
fields =
if entry.target do
fields ++ [%{label: "Target", value: entry.target.email}]
else
fields
end
fields =
if entry.metadata["role"] do
fields ++ [%{label: "Role", value: entry.metadata["role"]}]
else
fields
end
fields ++
[
%{
label: "Date",
value:
"#{Calendar.strftime(entry.inserted_at, "%b %d, %Y %H:%M")} (#{format_time_ago(entry.inserted_at)})"
}
]
end
}
>
<:toolbar_title>
<span class="text-sm text-base-content/60">{@total} activities</span>
</:toolbar_title>
<:toolbar_actions>
<button type="button" phx-click="clear_filters" class="btn btn-ghost btn-sm">
Clear filters
</button>
</: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" /> View
</.link>
</:card_actions>
<.table_default_header>
<.table_default_row hover={false}>
<.table_default_header_cell>Module</.table_default_header_cell>
<.table_default_header_cell>Mode</.table_default_header_cell>
<.table_default_header_cell>Activity</.table_default_header_cell>
<.table_default_header_cell>Actor</.table_default_header_cell>
<.table_default_header_cell>Details</.table_default_header_cell>
<.table_default_header_cell>Subject</.table_default_header_cell>
<.table_default_header_cell>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>
<%= 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">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>{Calendar.strftime(entry.inserted_at, "%b %d, %Y")}</div>
<div class="text-base-content/40">
{Calendar.strftime(entry.inserted_at, "%H:%M:%S")}
</div>
</.table_default_cell>
<.table_default_cell>
<.link
navigate={Routes.path("/admin/activity/#{entry.uuid}")}
class="btn btn-ghost btn-xs btn-square"
title="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>
<% end %>
<%!-- 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>