feat: reuse form_section in settings
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
89b02aeacf
commit
8512be0282
4 changed files with 209 additions and 169 deletions
|
|
@ -153,7 +153,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={@open}
|
aria-expanded={@open}
|
||||||
aria-controls={@id}
|
aria-controls={@id}
|
||||||
class="btn btn-ghost"
|
class="btn"
|
||||||
phx-click="toggle_dropdown"
|
phx-click="toggle_dropdown"
|
||||||
phx-target={@phx_target}
|
phx-target={@phx_target}
|
||||||
data-testid="dropdown-button"
|
data-testid="dropdown-button"
|
||||||
|
|
@ -236,6 +236,30 @@ defmodule MvWeb.CoreComponents do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a section in with a border similar to cards.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<.form_section title={gettext("Personal Data")}>
|
||||||
|
<p>input</p>
|
||||||
|
</form_section>
|
||||||
|
"""
|
||||||
|
attr :title, :string, required: true
|
||||||
|
slot :inner_block, required: true
|
||||||
|
|
||||||
|
def form_section(assigns) do
|
||||||
|
~H"""
|
||||||
|
<section class="mb-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-3">{@title}</h2>
|
||||||
|
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
||||||
|
{render_slot(@inner_block)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders an input with label and error messages.
|
Renders an input with label and error messages.
|
||||||
|
|
||||||
|
|
@ -434,7 +458,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
~H"""
|
~H"""
|
||||||
<header class={[@actions != [] && "flex items-center justify-between gap-6", "pb-4", @class]}>
|
<header class={[@actions != [] && "flex items-center justify-between gap-6", "pb-4", @class]}>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-semibold leading-8">
|
<h1 class="text-xl font-semibold leading-8">
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</h1>
|
</h1>
|
||||||
<p :if={@subtitle != []} class="text-sm text-base-content/70">
|
<p :if={@subtitle != []} class="text-sm text-base-content/70">
|
||||||
|
|
@ -474,6 +498,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
|
|
||||||
slot :col, required: true do
|
slot :col, required: true do
|
||||||
attr :label, :string
|
attr :label, :string
|
||||||
|
attr :class, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
slot :action, doc: "the slot for showing user actions in the last table column"
|
slot :action, doc: "the slot for showing user actions in the last table column"
|
||||||
|
|
@ -489,7 +514,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
<table class="table table-zebra">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th :for={col <- @col}>{col[:label]}</th>
|
<th :for={col <- @col} class={Map.get(col, :class)}>{col[:label]}</th>
|
||||||
<th :for={dyn_col <- @dynamic_cols}>
|
<th :for={dyn_col <- @dynamic_cols}>
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MvWeb.Components.SortHeaderComponent}
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
|
|
@ -510,7 +535,33 @@ defmodule MvWeb.CoreComponents do
|
||||||
<td
|
<td
|
||||||
:for={col <- @col}
|
:for={col <- @col}
|
||||||
phx-click={@row_click && @row_click.(row)}
|
phx-click={@row_click && @row_click.(row)}
|
||||||
class={["max-w-xs truncate", @row_click && "hover:cursor-pointer"]}
|
class={
|
||||||
|
col_class = Map.get(col, :class)
|
||||||
|
classes = ["max-w-xs"]
|
||||||
|
|
||||||
|
classes =
|
||||||
|
if col_class == nil || (col_class && !String.contains?(col_class, "text-center")) do
|
||||||
|
["truncate" | classes]
|
||||||
|
else
|
||||||
|
classes
|
||||||
|
end
|
||||||
|
|
||||||
|
classes =
|
||||||
|
if @row_click do
|
||||||
|
["hover:cursor-pointer" | classes]
|
||||||
|
else
|
||||||
|
classes
|
||||||
|
end
|
||||||
|
|
||||||
|
classes =
|
||||||
|
if col_class do
|
||||||
|
[col_class | classes]
|
||||||
|
else
|
||||||
|
classes
|
||||||
|
end
|
||||||
|
|
||||||
|
Enum.join(classes, " ")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{render_slot(col, @row_item.(row))}
|
{render_slot(col, @row_item.(row))}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -18,141 +18,149 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div id={@id}>
|
<div id={@id}>
|
||||||
<.form_section title={gettext("Custom Fields")}>
|
<.form_section title={gettext("Custom Fields")}>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p class="text-sm text-base-content/70">{gettext("These will appear in addition to other data when adding new members.")}</p>
|
<p class="text-sm text-base-content/70">
|
||||||
<div class="ml-auto">
|
{gettext("These will appear in addition to other data when adding new members.")}
|
||||||
<.button class="ml-auto" variant="primary" phx-click="new_custom_field" phx-target={@myself}>
|
</p>
|
||||||
<.icon name="hero-plus" /> {gettext("New Custom field")}
|
<div class="ml-auto">
|
||||||
</.button>
|
<.button
|
||||||
</div>
|
class="ml-auto"
|
||||||
</div>
|
variant="primary"
|
||||||
<%!-- Show form when creating or editing --%>
|
phx-click="new_custom_field"
|
||||||
<div :if={@show_form} class="mb-8">
|
|
||||||
<.live_component
|
|
||||||
module={MvWeb.CustomFieldLive.FormComponent}
|
|
||||||
id={@form_id}
|
|
||||||
custom_field={@editing_custom_field}
|
|
||||||
on_save={
|
|
||||||
fn custom_field, action -> send(self(), {:custom_field_saved, custom_field, action}) end
|
|
||||||
}
|
|
||||||
on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%!-- Hide table when form is visible --%>
|
|
||||||
<.table
|
|
||||||
:if={!@show_form}
|
|
||||||
id="custom_fields"
|
|
||||||
rows={@streams.custom_fields}
|
|
||||||
row_click={
|
|
||||||
fn {_id, custom_field} ->
|
|
||||||
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>
|
|
||||||
|
|
||||||
<:col :let={{_id, custom_field}} label={gettext("Value Type")}>
|
|
||||||
{@field_type_label.(custom_field.value_type)}
|
|
||||||
</:col>
|
|
||||||
|
|
||||||
<:col :let={{_id, custom_field}} label={gettext("Description")}>
|
|
||||||
{custom_field.description}
|
|
||||||
</:col>
|
|
||||||
|
|
||||||
<:col :let={{_id, custom_field}} label={gettext("Show in overview")} class="max-w-[9.375rem] text-center">
|
|
||||||
<span :if={custom_field.show_in_overview} class="badge badge-success">
|
|
||||||
{gettext("Yes")}
|
|
||||||
</span>
|
|
||||||
<span :if={!custom_field.show_in_overview} class="badge badge-ghost">
|
|
||||||
{gettext("No")}
|
|
||||||
</span>
|
|
||||||
</:col>
|
|
||||||
|
|
||||||
<:action :let={{_id, custom_field}}>
|
|
||||||
<.icon_button
|
|
||||||
icon="hero-pencil"
|
|
||||||
label={gettext("Edit custom field")}
|
|
||||||
size="sm"
|
|
||||||
phx-click={JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)}
|
|
||||||
/>
|
|
||||||
</:action>
|
|
||||||
|
|
||||||
<:action :let={{_id, custom_field}}>
|
|
||||||
<.icon_button
|
|
||||||
icon="hero-trash"
|
|
||||||
label={gettext("Delete custom field")}
|
|
||||||
size="sm"
|
|
||||||
class="btn-error"
|
|
||||||
phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}
|
|
||||||
/>
|
|
||||||
</:action>
|
|
||||||
</.table>
|
|
||||||
|
|
||||||
<%!-- Delete Confirmation Modal --%>
|
|
||||||
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
|
|
||||||
<div class="modal-box">
|
|
||||||
<h3 class="text-lg font-bold">{gettext("Delete Custom Field")}</h3>
|
|
||||||
|
|
||||||
<div class="py-4 space-y-4">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">
|
|
||||||
{ngettext(
|
|
||||||
"%{count} member has a value assigned for this custom field.",
|
|
||||||
"%{count} members have values assigned for this custom field.",
|
|
||||||
@custom_field_to_delete.assigned_members_count,
|
|
||||||
count: @custom_field_to_delete.assigned_members_count
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p class="mt-2 text-sm">
|
|
||||||
{gettext(
|
|
||||||
"All custom field values will be permanently deleted when you delete this custom field."
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="slug-confirmation" class="label">
|
|
||||||
<span class="label-text">
|
|
||||||
{gettext("To confirm deletion, please enter this text:")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="p-2 mb-2 font-mono text-lg font-bold break-all rounded bg-base-200">
|
|
||||||
{@custom_field_to_delete.slug}
|
|
||||||
</div>
|
|
||||||
<form phx-change="update_slug_confirmation" phx-target={@myself}>
|
|
||||||
<input
|
|
||||||
id="slug-confirmation"
|
|
||||||
name="slug"
|
|
||||||
type="text"
|
|
||||||
value={@slug_confirmation}
|
|
||||||
placeholder={gettext("Enter the text above to confirm")}
|
|
||||||
autocomplete="off"
|
|
||||||
phx-mounted={JS.focus()}
|
|
||||||
class="w-full input input-bordered"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<button phx-click="cancel_delete" phx-target={@myself} class="btn">
|
|
||||||
{gettext("Cancel")}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
phx-click="confirm_delete"
|
|
||||||
phx-target={@myself}
|
phx-target={@myself}
|
||||||
class="btn btn-error"
|
|
||||||
disabled={@slug_confirmation != @custom_field_to_delete.slug}
|
|
||||||
>
|
>
|
||||||
{gettext("Delete Custom Field and All Values")}
|
<.icon name="hero-plus" /> {gettext("New Custom field")}
|
||||||
</button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<%!-- Show form when creating or editing --%>
|
||||||
|
<div :if={@show_form} class="mb-8">
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.CustomFieldLive.FormComponent}
|
||||||
|
id={@form_id}
|
||||||
|
custom_field={@editing_custom_field}
|
||||||
|
on_save={
|
||||||
|
fn custom_field, action -> send(self(), {:custom_field_saved, custom_field, action}) end
|
||||||
|
}
|
||||||
|
on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%!-- Hide table when form is visible --%>
|
||||||
|
<.table
|
||||||
|
:if={!@show_form}
|
||||||
|
id="custom_fields"
|
||||||
|
rows={@streams.custom_fields}
|
||||||
|
row_click={
|
||||||
|
fn {_id, custom_field} ->
|
||||||
|
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>
|
||||||
|
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Value Type")}>
|
||||||
|
{@field_type_label.(custom_field.value_type)}
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Description")}>
|
||||||
|
{custom_field.description}
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:col
|
||||||
|
:let={{_id, custom_field}}
|
||||||
|
label={gettext("Show in overview")}
|
||||||
|
class="max-w-[9.375rem] text-center"
|
||||||
|
>
|
||||||
|
<span :if={custom_field.show_in_overview} class="badge badge-success">
|
||||||
|
{gettext("Yes")}
|
||||||
|
</span>
|
||||||
|
<span :if={!custom_field.show_in_overview} class="badge badge-ghost">
|
||||||
|
{gettext("No")}
|
||||||
|
</span>
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:action :let={{_id, custom_field}}>
|
||||||
|
<.link phx-click={
|
||||||
|
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
|
||||||
|
}>
|
||||||
|
{gettext("Edit")}
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
|
||||||
|
<:action :let={{_id, custom_field}}>
|
||||||
|
<.link phx-click={
|
||||||
|
JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)
|
||||||
|
}>
|
||||||
|
{gettext("Delete")}
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
|
||||||
|
<%!-- Delete Confirmation Modal --%>
|
||||||
|
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="text-lg font-bold">{gettext("Delete Custom Field")}</h3>
|
||||||
|
|
||||||
|
<div class="py-4 space-y-4">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">
|
||||||
|
{ngettext(
|
||||||
|
"%{count} member has a value assigned for this custom field.",
|
||||||
|
"%{count} members have values assigned for this custom field.",
|
||||||
|
@custom_field_to_delete.assigned_members_count,
|
||||||
|
count: @custom_field_to_delete.assigned_members_count
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-sm">
|
||||||
|
{gettext(
|
||||||
|
"All custom field values will be permanently deleted when you delete this custom field."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="slug-confirmation" class="label">
|
||||||
|
<span class="label-text">
|
||||||
|
{gettext("To confirm deletion, please enter this text:")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="p-2 mb-2 font-mono text-lg font-bold break-all rounded bg-base-200">
|
||||||
|
{@custom_field_to_delete.slug}
|
||||||
|
</div>
|
||||||
|
<form phx-change="update_slug_confirmation" phx-target={@myself}>
|
||||||
|
<input
|
||||||
|
id="slug-confirmation"
|
||||||
|
name="slug"
|
||||||
|
type="text"
|
||||||
|
value={@slug_confirmation}
|
||||||
|
placeholder={gettext("Enter the text above to confirm")}
|
||||||
|
autocomplete="off"
|
||||||
|
phx-mounted={JS.focus()}
|
||||||
|
class="w-full input input-bordered"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<button phx-click="cancel_delete" phx-target={@myself} class="btn">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
phx-click="confirm_delete"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-error"
|
||||||
|
disabled={@slug_confirmation != @custom_field_to_delete.slug}
|
||||||
|
>
|
||||||
|
{gettext("Delete Custom Field and All Values")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</.form_section>
|
</.form_section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -46,22 +46,22 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<%!-- Club Settings Section --%>
|
<%!-- Club Settings Section --%>
|
||||||
<.header>
|
<.form_section title={gettext("Club Settings")}>
|
||||||
{gettext("Club Settings")}
|
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
||||||
</.header>
|
<div class="w-100">
|
||||||
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
<.input
|
||||||
<.input
|
field={@form[:club_name]}
|
||||||
field={@form[:club_name]}
|
type="text"
|
||||||
type="text"
|
label={gettext("Association Name")}
|
||||||
label={gettext("Association Name")}
|
required
|
||||||
required
|
/>
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
|
||||||
{gettext("Save Settings")}
|
|
||||||
</.button>
|
|
||||||
</.form>
|
|
||||||
|
|
||||||
|
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||||
|
{gettext("Save Settings")}
|
||||||
|
</.button>
|
||||||
|
</.form>
|
||||||
|
</.form_section>
|
||||||
<%!-- Custom Fields Section --%>
|
<%!-- Custom Fields Section --%>
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MvWeb.CustomFieldLive.IndexComponent}
|
module={MvWeb.CustomFieldLive.IndexComponent}
|
||||||
|
|
|
||||||
|
|
@ -348,25 +348,6 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
defp return_path("show", nil), do: ~p"/members"
|
defp return_path("show", nil), do: ~p"/members"
|
||||||
defp return_path("show", member), do: ~p"/members/#{member.id}"
|
defp return_path("show", member), do: ~p"/members/#{member.id}"
|
||||||
|
|
||||||
# -----------------------------------------------------------------
|
|
||||||
# Helper Components
|
|
||||||
# -----------------------------------------------------------------
|
|
||||||
|
|
||||||
# Renders a form section box with border and title.
|
|
||||||
attr :title, :string, required: true
|
|
||||||
slot :inner_block, required: true
|
|
||||||
|
|
||||||
defp form_section(assigns) do
|
|
||||||
~H"""
|
|
||||||
<section class="mb-6">
|
|
||||||
<h2 class="text-lg font-semibold mb-3">{@title}</h2>
|
|
||||||
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
|
||||||
{render_slot(@inner_block)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------
|
# -----------------------------------------------------------------
|
||||||
# Helper Functions for Custom Fields
|
# Helper Functions for Custom Fields
|
||||||
# -----------------------------------------------------------------
|
# -----------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue