feat: type not editable

This commit is contained in:
carla 2026-02-18 16:42:54 +01:00
parent adea380d86
commit e47e266570
6 changed files with 180 additions and 14 deletions

View file

@ -10,7 +10,7 @@ defmodule Mv.Membership.CustomField do
## Attributes
- `name` - Unique identifier for the custom field (e.g., "phone_mobile", "birthday")
- `slug` - URL-friendly, immutable identifier automatically generated from name (e.g., "phone-mobile")
- `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`)
- `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`). Immutable after creation.
- `description` - Optional human-readable description
- `required` - If true, all members must have this custom field (future feature)
- `show_in_overview` - If true, this custom field will be displayed in the member overview table and can be sorted
@ -28,6 +28,7 @@ defmodule Mv.Membership.CustomField do
## Constraints
- Name must be unique across all custom fields
- Name maximum length: 100 characters
- `value_type` cannot be changed after creation (immutable)
- Deleting a custom field will cascade delete all associated custom field values
## Calculations
@ -59,7 +60,7 @@ defmodule Mv.Membership.CustomField do
end
actions do
defaults [:read, :update]
defaults [:read]
default_accept [:name, :value_type, :description, :required, :show_in_overview]
create :create do
@ -68,6 +69,21 @@ defmodule Mv.Membership.CustomField do
validate string_length(:slug, min: 1)
end
update :update do
accept [:name, :description, :required, :show_in_overview]
require_atomic? false
validate fn changeset, _context ->
if Ash.Changeset.changing_attribute?(changeset, :value_type) do
{:error,
field: :value_type,
message: "cannot be changed after creation"}
else
:ok
end
end
end
destroy :destroy_with_values do
primary? true
end

View file

@ -5,7 +5,7 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
## Features
- Create new custom field definitions
- Edit existing custom fields
- Select value type from supported types
- Select value type from supported types (only on create; immutable after creation)
- Set required flag
- Real-time validation
@ -44,15 +44,36 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
>
<.input field={@form[:name]} type="text" label={gettext("Name")} />
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
|> Enum.map(fn type -> {MvWeb.Translations.FieldTypes.label(type), type} end)
}
/>
<%= if @custom_field do %>
<%!-- Show value_type as read-only text when editing --%>
<div class="form-control">
<label class="label">
<span class="label-text">{gettext("Value type")}</span>
</label>
<div class="input input-bordered bg-base-200 text-base-content/70">
{MvWeb.Translations.FieldTypes.label(@custom_field.value_type)}
</div>
<label class="label">
<span class="label-text-alt text-base-content/70">
{gettext("Value type cannot be changed after creation")}
</span>
</label>
</div>
<% else %>
<%!-- Show value_type as select when creating --%>
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[
:one_of
]
|> Enum.map(fn type -> {MvWeb.Translations.FieldTypes.label(type), type} end)
}
/>
<% end %>
<.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input
@ -85,8 +106,16 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
@impl true
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
# Remove value_type from params when editing (it's immutable after creation)
cleaned_params =
if socket.assigns[:custom_field] do
Map.delete(custom_field_params, "value_type")
else
custom_field_params
end
{:noreply,
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, cleaned_params))}
end
@impl true
@ -94,7 +123,15 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
# Actor must be passed from parent (IndexComponent); component socket has no current_user
actor = socket.assigns[:actor]
case MvWeb.LiveHelpers.submit_form(socket.assigns.form, custom_field_params, actor) do
# Remove value_type from params when editing (it's immutable after creation)
cleaned_params =
if socket.assigns[:custom_field] do
Map.delete(custom_field_params, "value_type")
else
custom_field_params
end
case MvWeb.LiveHelpers.submit_form(socket.assigns.form, cleaned_params, actor) do
{:ok, custom_field} ->
action =
case socket.assigns.form.source.type do