Packages

phoenix_kit

1.7.0
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_settings.html.heex
Raw

lib/phoenix_kit_web/live/modules/entities/entities_settings.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 Settings")
)
}
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("Entities Settings")}</h1>
<p class="text-lg text-base-content">
{gettext("Configure the entities system behavior and preferences")}
</p>
</div>
</header>
<%!-- System Status Card --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-cog-6-tooth" class="w-6 h-6" /> {gettext("System Status")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<%!-- System Toggle --%>
<div>
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-lg font-semibold">{gettext("Entities System")}</h3>
<p class="text-sm text-base-content/70">
{gettext("Enable or disable the entire entities module")}
</p>
</div>
<span class={"badge #{setting_status_class(@settings.entities_enabled)}"}>
{setting_status_text(@settings.entities_enabled)}
</span>
</div>
<div class="flex gap-2">
<%= if @settings.entities_enabled do %>
<button
class="btn btn-error btn-sm"
phx-click="disable_entities"
data-confirm={
gettext(
"Are you sure you want to disable the entities system? This will make all entities and data inaccessible."
)
}
>
<.icon name="hero-x-mark" class="w-4 h-4 mr-1" /> {gettext("Disable System")}
</button>
<% else %>
<button
class="btn btn-success btn-sm"
phx-click="enable_entities"
>
<.icon name="hero-check" class="w-4 h-4 mr-1" /> {gettext("Enable System")}
</button>
<% end %>
</div>
</div>
<%!-- Quick Stats --%>
<div>
<h3 class="text-lg font-semibold mb-4">{gettext("Quick Stats")}</h3>
<div class="grid grid-cols-2 gap-4">
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Entities")}</div>
<div class="stat-value text-lg">{@entities_stats.total_entities}</div>
</div>
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Data Records")}</div>
<div class="stat-value text-lg">{@entities_stats.total_data_records}</div>
</div>
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Published")}</div>
<div class="stat-value text-lg">{@entities_stats.published_records}</div>
</div>
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Drafts")}</div>
<div class="stat-value text-lg">{@entities_stats.draft_records}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<%!-- Mirror & Export Card --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-arrow-path" class="w-6 h-6" /> {gettext("Mirror & Export")}
</h2>
<p class="text-base-content/70 mb-4">
{gettext(
"Sync entity definitions and data to filesystem for version control and backup."
)}
</p>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<%!-- Auto-Sync Toggles --%>
<div class="space-y-4">
<h3 class="text-lg font-semibold">{gettext("Auto-Sync Settings")}</h3>
<%!-- Mirror Definitions Toggle --%>
<div class="flex items-center justify-between p-3 bg-base-200 rounded-lg">
<div>
<p class="font-medium">{gettext("Mirror Entity Definitions")}</p>
<p class="text-sm text-base-content/70">
{gettext("Automatically export entity schemas on changes")}
</p>
</div>
<%= if @mirror_definitions_enabled do %>
<button
class="btn btn-success btn-sm"
phx-click="toggle_mirror_definitions"
disabled={@exporting}
>
<.icon name="hero-check" class="w-4 h-4" />
{gettext("On")}
</button>
<% else %>
<button
class="btn btn-outline btn-sm"
phx-click="toggle_mirror_definitions"
disabled={@exporting}
>
<.icon name="hero-x-mark" class="w-4 h-4" />
{gettext("Off")}
</button>
<% end %>
</div>
<%!-- Mirror Data Toggle (only visible if definitions enabled) --%>
<%= if @mirror_definitions_enabled do %>
<div class="flex items-center justify-between p-3 bg-base-200 rounded-lg">
<div>
<p class="font-medium">{gettext("Mirror Entity Data")}</p>
<p class="text-sm text-base-content/70">
{gettext("Also export entity records on changes")}
</p>
</div>
<%= if @mirror_data_enabled do %>
<button
class="btn btn-success btn-sm"
phx-click="toggle_mirror_data"
disabled={@exporting}
>
<.icon name="hero-check" class="w-4 h-4" />
{gettext("On")}
</button>
<% else %>
<button
class="btn btn-outline btn-sm"
phx-click="toggle_mirror_data"
disabled={@exporting}
>
<.icon name="hero-x-mark" class="w-4 h-4" />
{gettext("Off")}
</button>
<% end %>
</div>
<% end %>
<%!-- Export Path --%>
<div class="p-3 bg-base-200 rounded-lg">
<p class="font-medium mb-1">{gettext("Export Path")}</p>
<code class="text-sm text-base-content/70 break-all">{@mirror_path}</code>
</div>
<%!-- Export Now Button --%>
<button
class="btn btn-primary w-full"
phx-click="export_now"
disabled={@exporting}
>
<%= if @exporting do %>
<span class="loading loading-spinner loading-sm"></span>
{gettext("Exporting...")}
<% else %>
<.icon name="hero-arrow-up-tray" class="w-5 h-5 mr-2" />
{gettext("Export Now")}
<% end %>
</button>
</div>
<%!-- Export Statistics --%>
<div class="space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">{gettext("Export Statistics")}</h3>
<button
class="btn btn-ghost btn-xs"
phx-click="refresh_export_stats"
title={gettext("Refresh")}
>
<.icon name="hero-arrow-path" class="w-4 h-4" />
</button>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Definition Files")}</div>
<div class="stat-value text-lg">{@export_stats.definitions_count}</div>
</div>
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Data Files")}</div>
<div class="stat-value text-lg">{@export_stats.data_count}</div>
</div>
</div>
<%= if @export_stats.last_export do %>
<div class="text-sm text-base-content/70">
{gettext("Last export: %{time}", time: @export_stats.last_export)}
</div>
<% end %>
<%!-- Import Section --%>
<div class="divider">{gettext("Import")}</div>
<p class="text-sm text-base-content/70">
{gettext("Import entity definitions and data from JSON files in the mirror path.")}
</p>
<button
class="btn btn-outline btn-secondary w-full"
phx-click="show_import_modal"
disabled={@importing}
>
<%= if @importing do %>
<span class="loading loading-spinner loading-sm"></span>
{gettext("Importing...")}
<% else %>
<.icon name="hero-arrow-down-tray" class="w-5 h-5 mr-2" />
{gettext("Import from Files")}
<% end %>
</button>
</div>
</div>
</div>
</div>
<%!-- Import Modal --%>
<%= if @show_import_modal do %>
<div class="modal modal-open">
<div class="modal-box max-w-4xl max-h-[90vh]">
<h3 class="font-bold text-lg mb-4">
<.icon name="hero-arrow-down-tray" class="w-5 h-5 mr-2 inline" />
{gettext("Import Entities")}
</h3>
<%= if @import_preview do %>
<%!-- Summary Stats --%>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Definitions")}</div>
<div class="stat-value text-lg">{@import_preview.summary.definitions.total}</div>
<div class="stat-desc">
{gettext("%{new} new, %{identical} identical, %{conflicts} changed",
new: @import_preview.summary.definitions.new,
identical: @import_preview.summary.definitions.identical,
conflicts: @import_preview.summary.definitions.conflicts
)}
</div>
</div>
<div class="stat bg-base-200 rounded p-3">
<div class="stat-title text-xs">{gettext("Data Records")}</div>
<div class="stat-value text-lg">{@import_preview.summary.data.total}</div>
<div class="stat-desc">
{gettext("%{new} new, %{identical} identical, %{conflicts} changed",
new: @import_preview.summary.data.new,
identical: @import_preview.summary.data.identical,
conflicts: @import_preview.summary.data.conflicts
)}
</div>
</div>
</div>
<%!-- Entity Tabs --%>
<%= if length(@import_preview.entities) > 0 do %>
<div role="tablist" class="tabs tabs-bordered mb-4">
<%= for entity <- @import_preview.entities do %>
<button
role="tab"
class={"tab #{if @import_active_tab == entity.name, do: "tab-active", else: ""}"}
phx-click="set_import_tab"
phx-value-entity={entity.name}
>
{entity.name}
<span class={"badge badge-sm ml-2 #{if @import_active_tab == entity.name, do: "badge-primary", else: "badge-ghost"}"}>
{length(entity.data)}
</span>
</button>
<% end %>
</div>
<%!-- Active Entity Content --%>
<%= for entity <- @import_preview.entities do %>
<div class={if @import_active_tab == entity.name, do: "", else: "hidden"}>
<%!-- Definition Section --%>
<div class="bg-base-200 rounded-lg p-4 mb-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="font-semibold">{gettext("Definition")}</span>
<%= case entity.definition.action do %>
<% :create -> %>
<span class="badge badge-success badge-sm">{gettext("NEW")}</span>
<% :identical -> %>
<span class="badge badge-ghost badge-sm">{gettext("IDENTICAL")}</span>
<% :conflict -> %>
<span class="badge badge-warning badge-sm">{gettext("CHANGED")}</span>
<% _ -> %>
<span class="badge badge-error badge-sm">{gettext("ERROR")}</span>
<% end %>
</div>
<select
class="select select-sm select-bordered"
phx-change="set_definition_action"
phx-value-entity={entity.name}
name="action"
>
<option
value="skip"
selected={
get_definition_action(@import_selections, entity.name) == :skip
}
>
{gettext("Skip")}
</option>
<option
value="overwrite"
selected={
get_definition_action(@import_selections, entity.name) == :overwrite
}
>
{gettext("Import/Overwrite")}
</option>
<option
value="merge"
selected={
get_definition_action(@import_selections, entity.name) == :merge
}
>
{gettext("Merge")}
</option>
</select>
</div>
</div>
<%!-- Data Records Section --%>
<%= if length(entity.data) > 0 do %>
<div class="flex items-center justify-between mb-2">
<span class="font-semibold">
{gettext("Data Records")} ({length(entity.data)})
</span>
<div class="flex gap-2">
<button
class="btn btn-xs btn-ghost"
phx-click="set_all_records_action"
phx-value-entity={entity.name}
phx-value-action="skip"
>
{gettext("Skip All")}
</button>
<button
class="btn btn-xs btn-ghost"
phx-click="set_all_records_action"
phx-value-entity={entity.name}
phx-value-action="overwrite"
>
{gettext("Import All")}
</button>
</div>
</div>
<div class="overflow-y-auto max-h-64 border border-base-300 rounded-lg">
<table class="table table-sm table-zebra">
<thead class="sticky top-0 bg-base-200">
<tr>
<th>{gettext("Status")}</th>
<th>{gettext("Slug")}</th>
<th>{gettext("Title")}</th>
<th class="text-right">{gettext("Action")}</th>
</tr>
</thead>
<tbody>
<%= for record <- entity.data do %>
<tr>
<td>
<%= case record.action do %>
<% :create -> %>
<span class="badge badge-success badge-xs">
{gettext("NEW")}
</span>
<% :identical -> %>
<span class="badge badge-ghost badge-xs">
{gettext("IDENTICAL")}
</span>
<% :conflict -> %>
<span class="badge badge-warning badge-xs">
{gettext("CHANGED")}
</span>
<% _ -> %>
<span class="badge badge-error badge-xs">
{gettext("ERROR")}
</span>
<% end %>
</td>
<td class="font-mono text-xs">
<%= if record[:is_new_record] do %>
<span class="text-base-content/50">
{record[:display_slug]}
</span>
<span class="text-success text-xs block">
{record[:generated_slug]}
</span>
<% else %>
{record.slug}
<%= if record[:generated_slug] do %>
<span class="text-warning text-xs block">
{record[:generated_slug]}
</span>
<% end %>
<% end %>
</td>
<td class="truncate max-w-32">{record[:title] || "-"}</td>
<td class="text-right">
<select
class="select select-xs select-bordered"
phx-change="set_record_action"
phx-value-entity={entity.name}
phx-value-slug={record.slug}
name="action"
>
<option
value="skip"
selected={
get_record_action(
@import_selections,
entity.name,
record.slug
) == :skip
}
>
{gettext("Skip")}
</option>
<option
value="overwrite"
selected={
get_record_action(
@import_selections,
entity.name,
record.slug
) == :overwrite
}
>
{gettext("Import")}
</option>
<option
value="merge"
selected={
get_record_action(
@import_selections,
entity.name,
record.slug
) == :merge
}
>
{gettext("Merge")}
</option>
</select>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="text-center py-4 text-base-content/50">
{gettext("No data records")}
</div>
<% end %>
</div>
<% end %>
<% else %>
<div class="text-center py-8 text-base-content/50">
{gettext("No entities found to import")}
</div>
<% end %>
<% else %>
<div class="flex justify-center py-8">
<span class="loading loading-spinner loading-lg"></span>
</div>
<% end %>
<div class="modal-action">
<button class="btn btn-ghost" phx-click="hide_import_modal">
{gettext("Cancel")}
</button>
<button
class="btn btn-secondary btn-outline"
phx-click="do_import_entity"
phx-value-entity={@import_active_tab}
disabled={@import_preview == nil or @import_active_tab == nil}
>
<.icon name="hero-arrow-down-tray" class="w-4 h-4 mr-1" />
{gettext("Import This Entity")}
</button>
<button
class="btn btn-primary"
phx-click="do_import"
disabled={@import_preview == nil or length(@import_preview.entities) == 0}
>
<.icon name="hero-arrow-down-tray" class="w-4 h-4 mr-1" />
{gettext("Import All Entities")}
</button>
</div>
</div>
<div class="modal-backdrop" phx-click="hide_import_modal"></div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>