Packages

phoenix_kit

1.7.44
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 modules entities web data_form.html.heex
Raw

lib/modules/entities/web/data_form.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={
gettext("%{project_title} %{action} %{entity}",
project_title: @project_title,
action: if(@data_record.id, do: gettext("Edit"), else: gettext("New")),
entity: @entity.display_name
)
}
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 --%>
<.admin_page_header back={
PhoenixKit.Utils.Routes.path("/admin/entities/#{@entity.name}/data")
}>
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">
<%= if @data_record.id do %>
{gettext("Edit %{entity}", entity: @entity.display_name)}
<% else %>
{gettext("Create New %{entity}", entity: @entity.display_name)}
<% end %>
</h1>
<p class="text-sm text-base-content/60 mt-0.5">
<%= if @data_record.id do %>
{gettext("Update data for the %{entity} entity", entity: @entity.display_name)}
<% else %>
{gettext("Add data for the %{entity} entity", entity: @entity.display_name)}
<% end %>
</p>
</.admin_page_header>
<%!-- Readonly Banner --%>
<%= if @readonly? do %>
<div class="alert alert-info mb-6">
<.icon name="hero-eye" class="w-5 h-5" />
<span>
{gettext(
"This record is currently being edited by another user. You are in view-only mode."
)}
</span>
</div>
<% end %>
<%!-- Edit Mode Form --%>
<.form
:let={f}
for={@changeset}
phx-change="validate"
phx-debounce="500"
phx-submit="save"
class="space-y-8"
>
<%= if @multilang_enabled && length(@language_tabs) > 1 do %>
<%!-- Multilang: unified card with language tabs wrapping all content --%>
<div class="card bg-base-100 shadow-xl">
<%!-- Language tab bar --%>
<div class="card-body pb-0">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<.icon name="hero-language" class="w-5 h-5 text-primary" />
<h2 class="card-title text-lg m-0">{gettext("Content Language")}</h2>
</div>
<% primary_tab =
Enum.find(@language_tabs, fn t -> t.is_primary end) %>
<%= if primary_tab do %>
<div class="flex items-center gap-1.5 text-xs text-base-content/60">
<.icon name="hero-star-solid" class="w-3.5 h-3.5 text-primary" />
{gettext("Primary: %{lang}", lang: primary_tab.name)}
</div>
<% end %>
</div>
<% compact = length(@language_tabs) > 5 %>
<div
role="tablist"
class="flex flex-wrap items-center gap-1 bg-base-200 rounded-box p-1"
>
<%= if compact do %>
<%= for {tab, idx} <- Enum.with_index(@language_tabs) do %>
<%= if idx > 0 do %>
<span class="text-base-content/30">|</span>
<% end %>
<button
type="button"
role="tab"
phx-click="switch_language"
phx-value-lang={tab.code}
title={tab.name}
class={[
"tab gap-1 transition-all",
@current_lang == tab.code && "tab-active"
]}
>
<%= if tab.flag do %>
<span>{tab.flag}</span>
<% end %>
<span>{tab.short_code}</span>
<%= if tab.is_primary do %>
<.icon name="hero-star-solid" class="w-3 h-3 text-primary" />
<% end %>
</button>
<% end %>
<% else %>
<%= for tab <- @language_tabs do %>
<%= if tab.is_primary do %>
<button
type="button"
role="tab"
phx-click="switch_language"
phx-value-lang={tab.code}
class={[
"tab gap-1.5 transition-all font-medium",
@current_lang == tab.code && "tab-active"
]}
>
<%= if tab.flag do %>
<span>{tab.flag}</span>
<% end %>
<span>{tab.name}</span>
<.icon name="hero-star-solid" class="w-3 h-3 text-primary" />
</button>
<div class="divider divider-horizontal mx-0.5 h-6 self-center"></div>
<% else %>
<button
type="button"
role="tab"
phx-click="switch_language"
phx-value-lang={tab.code}
class={[
"tab gap-1.5 transition-all",
@current_lang == tab.code && "tab-active"
]}
>
<%= if tab.flag do %>
<span>{tab.flag}</span>
<% end %>
<span>{tab.name}</span>
</button>
<% end %>
<% end %>
<% end %>
</div>
<%= if @current_lang == @primary_language do %>
<p class="text-xs text-base-content/50 mt-3">
<.icon name="hero-information-circle" class="w-3.5 h-3.5 inline -mt-0.5" />
{gettext(
"This is the primary language. Other languages will use these values as defaults."
)}
</p>
<% else %>
<div class="alert alert-info mt-3 py-2 text-xs">
<.icon name="hero-information-circle" class="w-4 h-4" />
<span>
{gettext("Fields left empty will use the primary language value.")}
</span>
</div>
<% end %>
</div>
<div class="divider mx-6 my-0"></div>
<%!-- Tab content: Title (translatable) --%>
<div class="card-body pt-4 pb-4">
<h3 class="text-sm font-semibold text-base-content/70 uppercase tracking-wide mb-3">
<.icon name="hero-information-circle" class="w-4 h-4 inline -mt-0.5" />
{gettext("Basic Information")}
</h3>
<div>
<%= if @current_lang == @primary_language do %>
<.label for="phoenix_kit_entity_data_title">
{gettext("Title")} *
</.label>
<input
type="text"
name="phoenix_kit_entity_data[title]"
id="phoenix_kit_entity_data_title"
value={Ecto.Changeset.get_field(@changeset, :title) || ""}
placeholder={gettext("Enter a title for this record")}
class="input input-bordered w-full"
phx-debounce="300"
required
disabled={@readonly?}
/>
<% else %>
<% lang_data =
Multilang.get_raw_language_data(
Ecto.Changeset.get_field(@changeset, :data),
@current_lang
) %>
<.label for={"phoenix_kit_entity_data_title_#{@current_lang}"}>
{gettext("Title")}
</.label>
<input
type="text"
name="phoenix_kit_entity_data[lang_title]"
id={"phoenix_kit_entity_data_title_#{@current_lang}"}
value={lang_data["_title"]}
placeholder={
Ecto.Changeset.get_field(@changeset, :title) ||
gettext("Enter a title for this record")
}
class="input input-bordered w-full"
phx-debounce="300"
disabled={@readonly?}
/>
<% end %>
</div>
</div>
<div class="divider mx-6 my-0"></div>
<%!-- Tab content: Custom Fields (translatable) --%>
<div class="card-body pt-4">
<h3 class="text-sm font-semibold text-base-content/70 uppercase tracking-wide mb-3">
<.icon name="hero-list-bullet" class="w-4 h-4 inline -mt-0.5" />
{gettext("Custom Fields")}
</h3>
<%!-- Dynamic form fields generated by FormBuilder --%>
{PhoenixKit.Modules.Entities.FormBuilder.build_fields(@entity, f,
wrapper_class: "mb-6",
disabled: @readonly?,
lang_code: if(@multilang_enabled, do: @current_lang, else: nil)
)}
</div>
</div>
<%!-- Record Settings (non-translatable, separate card) --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-lg mb-4">
<.icon name="hero-cog-6-tooth" class="w-5 h-5" />
{gettext("Record Settings")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<%!-- Slug with Generator --%>
<div>
<.label for="phoenix_kit_entity_data_slug">
{gettext("Slug (URL-friendly identifier)")}
<% slug_disabled =
@readonly? ||
(@multilang_enabled && @current_lang != @primary_language) %>
<button
type="button"
class="btn btn-ghost btn-xs ml-2"
phx-click="generate_slug"
title={
if slug_disabled && @multilang_enabled &&
@current_lang != @primary_language,
do: gettext("Slug is generated from the primary language title"),
else: gettext("Generate from title")
}
disabled={slug_disabled}
>
<.icon name="hero-arrow-path" class="w-3 h-3" /> {gettext("Generate")}
</button>
</.label>
<input
type="text"
name="phoenix_kit_entity_data[slug]"
id="phoenix_kit_entity_data_slug"
value={Ecto.Changeset.get_field(@changeset, :slug) || ""}
placeholder={gettext("auto-generated-slug")}
class="input input-bordered w-full"
pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
title={gettext("Use lowercase letters, numbers, and hyphens only.")}
phx-debounce="300"
disabled={@readonly?}
/>
<.label class="label">
<span class="label-text-alt">
{gettext("Leave empty to auto-generate from title")}
</span>
</.label>
</div>
<%!-- Status --%>
<div>
<.label for="phoenix_kit_entity_data_status">{gettext("Status")}</.label>
<select
name="phoenix_kit_entity_data[status]"
class="select select-bordered w-full"
disabled={@readonly?}
>
<option
value="draft"
selected={Ecto.Changeset.get_field(@changeset, :status) == "draft"}
>
{gettext("Draft")}
</option>
<option
value="published"
selected={Ecto.Changeset.get_field(@changeset, :status) == "published"}
>
{gettext("Published")}
</option>
<option
value="archived"
selected={Ecto.Changeset.get_field(@changeset, :status) == "archived"}
>
{gettext("Archived")}
</option>
</select>
</div>
<%!-- Entity Type (Read-only) --%>
<div>
<.label>{gettext("Entity Type")}</.label>
<div class="input input-bordered w-full bg-base-200 flex items-center">
<%= if @entity.icon do %>
<.icon name={@entity.icon} class="w-4 h-4 mr-2" />
<% end %>
{@entity.display_name}
</div>
<input
type="hidden"
name="phoenix_kit_entity_data[entity_id]"
value={@entity.id}
/>
<input
type="hidden"
name="phoenix_kit_entity_data[entity_uuid]"
value={@entity.uuid}
/>
</div>
</div>
</div>
</div>
<% else %>
<%!-- Non-multilang: separate cards (original layout) --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-information-circle" class="w-6 h-6" />
{gettext("Basic Information")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<%!-- Title --%>
<div>
<.label for="phoenix_kit_entity_data_title">{gettext("Title")} *</.label>
<input
type="text"
name="phoenix_kit_entity_data[title]"
id="phoenix_kit_entity_data_title"
value={Ecto.Changeset.get_field(@changeset, :title) || ""}
placeholder={gettext("Enter a title for this record")}
class="input input-bordered w-full"
phx-debounce="300"
required
disabled={@readonly?}
/>
</div>
<%!-- Slug with Generator --%>
<div>
<.label for="phoenix_kit_entity_data_slug">
{gettext("Slug (URL-friendly identifier)")}
<button
type="button"
class="btn btn-ghost btn-xs ml-2"
phx-click="generate_slug"
title={gettext("Generate from title")}
disabled={@readonly?}
>
<.icon name="hero-arrow-path" class="w-3 h-3" /> {gettext("Generate")}
</button>
</.label>
<input
type="text"
name="phoenix_kit_entity_data[slug]"
id="phoenix_kit_entity_data_slug"
value={Ecto.Changeset.get_field(@changeset, :slug) || ""}
placeholder={gettext("auto-generated-slug")}
class="input input-bordered w-full"
pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
title={gettext("Use lowercase letters, numbers, and hyphens only.")}
phx-debounce="300"
disabled={@readonly?}
/>
<.label class="label">
<span class="label-text-alt">
{gettext("Leave empty to auto-generate from title")}
</span>
</.label>
</div>
<%!-- Status --%>
<div>
<.label for="phoenix_kit_entity_data_status">{gettext("Status")}</.label>
<select
name="phoenix_kit_entity_data[status]"
class="select select-bordered w-full"
disabled={@readonly?}
>
<option
value="draft"
selected={Ecto.Changeset.get_field(@changeset, :status) == "draft"}
>
{gettext("Draft")}
</option>
<option
value="published"
selected={Ecto.Changeset.get_field(@changeset, :status) == "published"}
>
{gettext("Published")}
</option>
<option
value="archived"
selected={Ecto.Changeset.get_field(@changeset, :status) == "archived"}
>
{gettext("Archived")}
</option>
</select>
</div>
<%!-- Entity Type (Read-only) --%>
<div>
<.label>{gettext("Entity Type")}</.label>
<div class="input input-bordered w-full bg-base-200 flex items-center">
<%= if @entity.icon do %>
<.icon name={@entity.icon} class="w-4 h-4 mr-2" />
<% end %>
{@entity.display_name}
</div>
<input
type="hidden"
name="phoenix_kit_entity_data[entity_id]"
value={@entity.id}
/>
<input
type="hidden"
name="phoenix_kit_entity_data[entity_uuid]"
value={@entity.uuid}
/>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">
<.icon name="hero-list-bullet" class="w-6 h-6" /> {gettext("Custom Fields")}
</h2>
<%!-- Dynamic form fields generated by FormBuilder --%>
{PhoenixKit.Modules.Entities.FormBuilder.build_fields(@entity, f,
wrapper_class: "mb-6",
disabled: @readonly?,
lang_code: nil
)}
</div>
</div>
<% end %>
<%!-- Form Actions --%>
<div class="flex justify-between items-center">
<div class="flex gap-2">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/entities/#{@entity.name}/data")}
class="btn btn-outline"
>
{gettext("Cancel")}
</.link>
<button type="button" phx-click="reset" class="btn btn-warning" disabled={@readonly?}>
<.icon name="hero-arrow-path" class="w-4 h-4" />
{gettext("Reset Changes")}
</button>
</div>
<button
type="submit"
class="btn btn-primary"
disabled={@readonly?}
>
<.icon name="hero-check" class="w-4 h-4 mr-2" />
<%= if @data_record.id do %>
{gettext("Update %{entity}", entity: @entity.display_name)}
<% else %>
{gettext("Create %{entity}", entity: @entity.display_name)}
<% end %>
</button>
</div>
</.form>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>