Packages

phoenix_kit

1.4.8
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
phoenix_kit lib phoenix_kit_web live modules entities entities.html.heex
Raw

lib/phoenix_kit_web/live/modules/entities/entities.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={
gettext("%{project_title} %{section}",
project_title: @project_title,
section: gettext("Entities")
)
}
current_path={@url_path}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="container flex flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<header class="w-full relative mb-6">
<%!-- Back Button (Left aligned) --%>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/modules")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<PhoenixKitWeb.Components.Core.Icons.icon_arrow_left /> {gettext("Back to Modules")}
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">{gettext("Entity Manager")}</h1>
<p class="text-lg text-base-content">
{gettext("Create and manage custom content types with dynamic fields")}
</p>
</div>
</header>
<%!-- Stats Cards --%>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="bg-primary text-primary-content rounded-2xl p-6 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105">
<div class="flex items-center justify-between mb-4">
<div class="p-2 bg-base-100/20 rounded-lg">
<.icon name="hero-rectangle-stack" class="w-6 h-6" />
</div>
</div>
<div class="text-3xl font-bold mb-2">{@total_entities}</div>
<div class="opacity-90 font-medium">{gettext("Total Entities")}</div>
<div class="opacity-70 text-xs mt-1">{gettext("Content types defined")}</div>
</div>
<div class="bg-success text-success-content rounded-2xl p-6 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105">
<div class="flex items-center justify-between mb-4">
<div class="p-2 bg-base-100/20 rounded-lg">
<.icon name="hero-bolt" class="w-6 h-6" />
</div>
</div>
<div class="text-3xl font-bold mb-2">{@active_entities}</div>
<div class="opacity-90 font-medium">{gettext("Published")}</div>
<div class="opacity-70 text-xs mt-1">{gettext("Ready for data creation")}</div>
</div>
<div class="bg-secondary text-secondary-content rounded-2xl p-6 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105">
<div class="flex items-center justify-between mb-4">
<div class="p-2 bg-base-100/20 rounded-lg">
<.icon name="hero-circle-stack" class="w-6 h-6" />
</div>
</div>
<div class="text-3xl font-bold mb-2">{@total_data_records}</div>
<div class="opacity-90 font-medium">{gettext("Data Records")}</div>
<div class="opacity-70 text-xs mt-1">{gettext("Across all entities")}</div>
</div>
</div>
<%!-- Action Bar --%>
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<div>
<h2 class="text-2xl font-semibold text-base-content">{gettext("All Entities")}</h2>
<p class="text-base-content/70">
{gettext("Manage custom content types and field definitions")}
</p>
</div>
<div class="flex gap-2 items-center">
<%!-- View Mode Toggle --%>
<div class="join">
<button
type="button"
phx-click="toggle_view_mode"
phx-value-mode="card"
class={["btn join-item", @view_mode == "card" && "btn-active"]}
title={gettext("Card view")}
>
<.icon name="hero-squares-2x2" class="w-4 h-4" />
</button>
<button
type="button"
phx-click="toggle_view_mode"
phx-value-mode="table"
class={["btn join-item", @view_mode == "table" && "btn-active"]}
title={gettext("Table view")}
>
<.icon name="hero-bars-3-bottom-left" class="w-4 h-4" />
</button>
</div>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/new")}
class="btn btn-primary"
>
<.icon name="hero-plus" class="w-4 h-4 mr-2" /> {gettext("New Entity")}
</.link>
</div>
</div>
<%!-- Filters Section --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title text-xl mb-4">
<.icon name="hero-funnel" class="w-5 h-5" /> {gettext("Filters & Search")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<%!-- Status Filter --%>
<div>
<label class="label">
<span class="label-text">{gettext("Filter by Status")}</span>
</label>
<.form for={%{}} phx-change="filter_by_status">
<select class="select select-bordered w-full" name="status">
<option value="all" selected={@selected_status == "all"}>
{gettext("All Statuses")}
</option>
<option value="published" selected={@selected_status == "published"}>
{gettext("Published")}
</option>
<option value="draft" selected={@selected_status == "draft"}>
{gettext("Draft")}
</option>
<option value="archived" selected={@selected_status == "archived"}>
{gettext("Archived")}
</option>
</select>
</.form>
</div>
<%!-- Search --%>
<div>
<label class="label">
<span class="label-text">{gettext("Search Entities")}</span>
</label>
<.form for={%{}} phx-change="search" phx-submit="search" class="join w-full">
<input
type="text"
name="search[term]"
value={@search_term}
placeholder={gettext("Search by name or display name...")}
class="input input-bordered join-item flex-1"
/>
<button type="submit" class="btn btn-primary join-item">
<.icon name="hero-magnifying-glass" class="w-4 h-4" />
</button>
</.form>
</div>
</div>
</div>
</div>
<%!-- Entities Grid --%>
<%= if Enum.empty?(@entities) do %>
<%!-- Empty State --%>
<div class="card bg-base-100 shadow-xl border-2 border-dashed border-base-300">
<div class="card-body text-center py-12">
<div class="text-6xl mb-4 opacity-50"><.icon name="hero-cube" class="w-6 h-6" /></div>
<%= if @total_entities == 0 do %>
<%!-- No entities exist at all --%>
<h3 class="text-2xl font-semibold text-base-content/60 mb-4">
{gettext("No Entities Yet")}
</h3>
<p class="text-base-content/50 mb-6 max-w-md mx-auto">
{gettext(
"Get started by creating your first custom content type. Think blog posts, products, team members, or any structured content you need."
)}
</p>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/new")}
class="btn btn-primary btn-lg"
>
<.icon name="hero-plus" class="w-5 h-5 mr-2" /> {gettext("Create Your First Entity")}
</.link>
<% else %>
<%= if @selected_status != "all" || @search_term != "" do %>
<%!-- Entities exist but filters exclude them --%>
<h3 class="text-2xl font-semibold text-base-content/60 mb-4">
{gettext("No Entities Found")}
</h3>
<p class="text-base-content/50 mb-6 max-w-md mx-auto">
{gettext(
"No entities match your current filters. Try adjusting your search criteria or clearing the filters."
)}
</p>
<button phx-click="clear_filters" class="btn btn-outline btn-lg">
<.icon name="hero-x-mark" class="w-5 h-5 mr-2" /> {gettext("Clear Filters")}
</button>
<% else %>
<%!-- True empty state --%>
<h3 class="text-2xl font-semibold text-base-content/60 mb-4">
{gettext("No Entities Yet")}
</h3>
<p class="text-base-content/50 mb-6 max-w-md mx-auto">
{gettext(
"Get started by creating your first custom content type. Think blog posts, products, team members, or any structured content you need."
)}
</p>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/new")}
class="btn btn-primary btn-lg"
>
<.icon name="hero-plus" class="w-5 h-5 mr-2" /> {gettext(
"Create Your First Entity"
)}
</.link>
<% end %>
<% end %>
</div>
</div>
<% else %>
<%!-- Entities List --%>
<%= if @view_mode == "table" do %>
<%!-- Table View --%>
<.table_default variant="zebra" size="sm">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>{gettext("Entity")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Slug")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Status")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Fields")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Created")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Actions")}</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for entity <- @entities do %>
<.table_default_row>
<.table_default_cell>
<div class="flex items-center gap-3">
<div class="text-2xl">
<%= if entity.icon do %>
<.icon name={entity.icon} class="w-6 h-6" />
<% else %>
<.icon name="hero-cube" class="w-6 h-6" />
<% end %>
</div>
<div>
<div class="font-bold">{entity.display_name}</div>
<%= if entity.description do %>
<div class="text-sm opacity-50 line-clamp-1">
{entity.description}
</div>
<% end %>
</div>
</div>
</.table_default_cell>
<.table_default_cell>
<code class="text-xs bg-base-200 px-2 py-1 rounded">{entity.name}</code>
</.table_default_cell>
<.table_default_cell>
<%= case entity.status do %>
<% "published" -> %>
<span class="badge badge-success badge-sm">
{gettext("Published")}
</span>
<% "draft" -> %>
<span class="badge badge-warning badge-sm">{gettext("Draft")}</span>
<% "archived" -> %>
<span class="badge badge-neutral badge-sm">
{gettext("Archived")}
</span>
<% _ -> %>
<span class="badge badge-ghost badge-sm">{gettext("Unknown")}</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<div class="flex items-center gap-1">
<.icon name="hero-list-bullet" class="w-4 h-4" />
<span>
{length(entity.fields_definition || [])}
</span>
</div>
</.table_default_cell>
<.table_default_cell>
<span class="text-sm">
{PhoenixKit.Utils.Date.format_date_with_user_format(entity.date_created)}
</span>
</.table_default_cell>
<.table_default_cell>
<div class="flex gap-2">
<.link
navigate={
PhoenixKit.Utils.Routes.locale_aware_path(
assigns,
"/admin/entities/#{entity.name}/data"
)
}
class="btn btn-ghost btn-xs"
title={gettext("View Data")}
>
<.icon name="hero-eye" class="w-4 h-4" />
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/#{entity.id}/edit")}
class="btn btn-ghost btn-xs"
title={gettext("Edit")}
>
<.icon name="hero-pencil" class="w-4 h-4" />
</.link>
<%= if entity.status == "archived" do %>
<button
class="btn btn-ghost btn-xs text-success"
phx-click="restore_entity"
phx-value-id={entity.id}
title={gettext("Restore")}
>
<.icon name="hero-arrow-path" class="w-4 h-4" />
</button>
<% else %>
<button
class="btn btn-ghost btn-xs text-error"
phx-click="archive_entity"
phx-value-id={entity.id}
title={gettext("Archive")}
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
<% end %>
</div>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
<% else %>
<%!-- Card View --%>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<%= for entity <- @entities do %>
<div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow">
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="flex items-center">
<div class="text-2xl mr-3">
<%= if entity.icon do %>
<.icon name={entity.icon} class="w-6 h-6" />
<% else %>
<.icon name="hero-cube" class="w-6 h-6" />
<% end %>
</div>
<div>
<h3 class="card-title text-lg">{entity.display_name}</h3>
<p class="text-sm text-base-content/60">
{entity.name}
</p>
</div>
</div>
<%!-- Status Badge --%>
<div>
<%= case entity.status do %>
<% "published" -> %>
<span class="badge badge-success badge-sm">{gettext("Published")}</span>
<% "draft" -> %>
<span class="badge badge-warning badge-sm">{gettext("Draft")}</span>
<% "archived" -> %>
<span class="badge badge-neutral badge-sm">{gettext("Archived")}</span>
<% _ -> %>
<span class="badge badge-ghost badge-sm">{gettext("Unknown")}</span>
<% end %>
</div>
</div>
<%= if entity.description do %>
<p class="text-sm text-base-content/70 mb-4 line-clamp-2">
{entity.description}
</p>
<% end %>
<%!-- Field Count --%>
<div class="flex items-center justify-between text-sm text-base-content/60 mb-4">
<div class="flex items-center">
<.icon name="hero-list-bullet" class="w-4 h-4 mr-1" />
<span>
{length(entity.fields_definition || [])}
{if length(entity.fields_definition || []) == 1,
do: gettext("field"),
else: gettext("fields")}
</span>
</div>
<%= if entity.creator do %>
<span class="badge badge-outline badge-xs">
{gettext("by")} {entity.creator.email}
</span>
<% end %>
</div>
<%!-- Actions --%>
<div class="card-actions justify-end">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/#{entity.name}/data")}
class="btn btn-outline btn-sm"
>
<.icon name="hero-eye" class="w-4 h-4 mr-1" /> {gettext("View Data")}
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/#{entity.id}/edit")}
class="btn btn-primary btn-sm"
>
<.icon name="hero-pencil" class="w-4 h-4 mr-1" /> {gettext("Edit")}
</.link>
<%!-- Archive/Restore Button --%>
<%= if entity.status == "archived" do %>
<button
class="btn btn-success btn-sm"
phx-click="restore_entity"
phx-value-id={entity.id}
title={gettext("Restore entity")}
>
<.icon name="hero-arrow-path" class="w-4 h-4" />
</button>
<% else %>
<button
class="btn btn-error btn-sm"
phx-click="archive_entity"
phx-value-id={entity.id}
title={gettext("Archive entity")}
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
<% end %>
</div>
<%!-- Created Date --%>
<div class="text-xs text-base-content/50 mt-2 pt-2 border-t border-base-300">
{gettext("Created")} {PhoenixKit.Utils.Date.format_date_with_user_format(
entity.date_created
)}
</div>
</div>
</div>
<% end %>
</div>
<% end %>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>