From f9d6936274eed813d2a462fa8f3a99939bb0c84c Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 4 Mar 2026 14:50:31 +0100 Subject: [PATCH] Membership fee settings: row-click table, compact default layout --- .../live/membership_fee_settings_live.ex | 375 +++++++++--------- .../membership_fee_type_live/index_test.exs | 4 +- 2 files changed, 180 insertions(+), 199 deletions(-) diff --git a/lib/mv_web/live/membership_fee_settings_live.ex b/lib/mv_web/live/membership_fee_settings_live.ex index db044b5..f95fa8a 100644 --- a/lib/mv_web/live/membership_fee_settings_live.ex +++ b/lib/mv_web/live/membership_fee_settings_live.ex @@ -151,24 +151,22 @@ defmodule MvWeb.MembershipFeeSettingsLive do -
- <%!-- Settings Form --%> -
-
-

- <.icon name="hero-cog-6-tooth" class="size-5" /> - {gettext("Global Settings")} + <%!-- One card: default setting + fee types table --%> +
+
+ <%!-- Default setting: one row, clear section title and split hints --%> + <.form + for={@form} + phx-change="validate" + phx-submit="save" + class="space-y-2" + > +

+ {gettext("Default settings")}

- - <.form - for={@form} - phx-change="validate" - phx-submit="save" - class="space-y-6" - > - <%!-- Default Membership Fee Type --%> -
-
- - <%!-- Examples Card (collapsible) --%> -
-
-
- - <.icon name="hero-chevron-right" class="size-5 transition group-open:rotate-90" /> - <.icon name="hero-light-bulb" class="size-5" /> - {gettext("Examples")} - - -
- <.example_section - title={gettext("Yearly Interval - Joining Cycle Included")} - joining_date="15.03.2023" - include_joining={true} - start_date="01.01.2023" - periods={["2023", "2024", "2025"]} - note={gettext("Member pays for the year they joined")} - /> - -
- - <.example_section - title={gettext("Yearly Interval - Joining Cycle Excluded")} - joining_date="15.03.2023" - include_joining={false} - start_date="01.01.2024" - periods={["2024", "2025"]} - note={gettext("Member pays from the next full year")} - /> - -
- - <.example_section - title={gettext("Quarterly Interval - Joining Cycle Excluded")} - joining_date="15.05.2024" - include_joining={false} - start_date="01.07.2024" - periods={["Q3/2024", "Q4/2024", "Q1/2025"]} - note={gettext("Member pays from the next full quarter")} - /> - -
- - <.example_section - title={gettext("Monthly Interval - Joining Cycle Included")} - joining_date="15.03.2024" - include_joining={true} - start_date="01.03.2024" - periods={["03/2024", "04/2024", "05/2024", "..."]} - note={gettext("Member pays from the joining month")} - /> +
+ <.button type="submit" variant="primary"> + <.icon name="hero-check" class="size-5" /> + {gettext("Save Settings")} +
-
-
-
-
+

- <%!-- Fee Types Table --%> -
-

{gettext("Membership Fee Types")}

- <.table - id="membership_fee_types" - rows={@membership_fee_types} - row_id={fn mft -> "mft-#{mft.id}" end} - > - <:col :let={mft} label={gettext("Name")}> - {mft.name} -

{mft.description}

- +
    +
  • {gettext("Default type: Assigned to new members; can be changed per member.")}
  • +
  • + {gettext( + "Include joining cycle: When active, members pay from their joining cycle; when inactive, from the next full cycle." + )} +
  • +
+ - <:col :let={mft} label={gettext("Amount")}> - {MembershipFeeHelpers.format_currency(mft.amount)} - +
- <:col :let={mft} label={gettext("Interval")}> - <.badge variant="neutral" style="outline"> - {MembershipFeeHelpers.format_interval(mft.interval)} - - + <%!-- Fee types table: row click opens edit --%> +

{gettext("Membership Fee Types")}

+ <.table + id="membership_fee_types" + rows={@membership_fee_types} + row_id={fn mft -> "mft-#{mft.id}" end} + row_click={ + fn mft -> + Phoenix.LiveView.JS.navigate(~p"/membership_fee_settings/#{mft.id}/edit_fee_type") + end + } + row_tooltip={gettext("Click to edit membership fee type")} + > + <:col :let={mft} label={gettext("Name")}> + {mft.name} +

{mft.description}

+ - <:col :let={mft} label={gettext("Members")}> - {get_member_count(mft, @member_counts)} - + <:col :let={mft} label={gettext("Amount")}> + {MembershipFeeHelpers.format_currency(mft.amount)} + - <:action :let={mft}> - <.tooltip content={gettext("Edit membership fee type")} position="left"> - <.button - variant="ghost" - size="sm" - navigate={~p"/membership_fee_settings/#{mft.id}/edit_fee_type"} - aria-label={gettext("Edit membership fee type")} - > - <.icon name="hero-pencil" class="size-4" /> - - - + <:col :let={mft} label={gettext("Interval")}> + <.badge variant="neutral" style="outline"> + {MembershipFeeHelpers.format_interval(mft.interval)} + + - <:action :let={mft}> - <.tooltip - :if={get_member_count(mft, @member_counts) > 0} - content={ - gettext("Cannot delete - %{count} member(s) assigned", - count: get_member_count(mft, @member_counts) - ) - } - position="left" - > - + + <.button + :if={get_member_count(mft, @member_counts) == 0} + variant="danger" + size="sm" + phx-click="delete" + phx-value-id={mft.id} + data-confirm={gettext("Are you sure?")} + aria-label={gettext("Delete Membership Fee Type")} > <.icon name="hero-trash" class="size-4" /> - - - <.button - :if={get_member_count(mft, @member_counts) == 0} - variant="danger" - size="sm" - phx-click="delete" - phx-value-id={mft.id} - data-confirm={gettext("Are you sure?")} - aria-label={gettext("Delete Membership Fee Type")} - > - <.icon name="hero-trash" class="size-4" /> - - - + + + +
+
-
- - <.icon name="hero-information-circle" class="size-5" /> - {gettext("About Membership Fee Types")} - -
-

- {gettext( - "Membership fee types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation." - )} -

-
    -
  • - {gettext("Name & Amount")} - - {gettext("Can be changed at any time. Amount changes affect future periods only.")} -
  • -
  • - {gettext("Interval")} - - {gettext( - "Fixed after creation. Members can only switch between types with the same interval." - )} -
  • -
  • - {gettext("Deletion")} - - {gettext("Only possible if no members are assigned to this type.")} -
  • -
-
-
+ <%!-- About membership fee types (info above examples) --%> +
+

+ {gettext( + "Membership fee types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation." + )} +

+
    +
  • + {gettext("Name & Amount")} + - {gettext("Can be changed at any time. Amount changes affect future periods only.")} +
  • +
  • + {gettext("Interval")} + - {gettext( + "Fixed after creation. Members can only switch between types with the same interval." + )} +
  • +
  • + {gettext("Deletion")} + - {gettext("Only possible if no members are assigned to this type.")} +
  • +
+
+ + <%!-- Examples (collapsible) --%> +
+
+
+ + <.icon name="hero-chevron-right" class="size-5 transition group-open:rotate-90" /> + <.icon name="hero-light-bulb" class="size-5" /> + {gettext("Examples")} + + +
+ <.example_section + title={gettext("Yearly Interval - Joining Cycle Included")} + joining_date="15.03.2023" + include_joining={true} + start_date="01.01.2023" + periods={["2023", "2024", "2025"]} + note={gettext("Member pays for the year they joined")} + /> + +
+ + <.example_section + title={gettext("Yearly Interval - Joining Cycle Excluded")} + joining_date="15.03.2023" + include_joining={false} + start_date="01.01.2024" + periods={["2024", "2025"]} + note={gettext("Member pays from the next full year")} + /> + +
+ + <.example_section + title={gettext("Quarterly Interval - Joining Cycle Excluded")} + joining_date="15.05.2024" + include_joining={false} + start_date="01.07.2024" + periods={["Q3/2024", "Q4/2024", "Q1/2025"]} + note={gettext("Member pays from the next full quarter")} + /> + +
+ + <.example_section + title={gettext("Monthly Interval - Joining Cycle Included")} + joining_date="15.03.2024" + include_joining={true} + start_date="01.03.2024" + periods={["03/2024", "04/2024", "05/2024", "..."]} + note={gettext("Member pays from the joining month")} + /> +
+
+
""" diff --git a/test/mv_web/live/membership_fee_type_live/index_test.exs b/test/mv_web/live/membership_fee_type_live/index_test.exs index c9bb7ca..c65341f 100644 --- a/test/mv_web/live/membership_fee_type_live/index_test.exs +++ b/test/mv_web/live/membership_fee_type_live/index_test.exs @@ -93,14 +93,14 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do assert to == "/membership_fee_settings/new_fee_type" end - test "edit button per row navigates to edit form", %{conn: conn, current_user: admin_user} do + test "row click navigates to edit form", %{conn: conn, current_user: admin_user} do fee_type = create_fee_type(%{interval: :yearly}, admin_user) {:ok, view, _html} = live(conn, "/membership_fee_settings") {:error, {:live_redirect, %{to: to}}} = view - |> element("a[href='/membership_fee_settings/#{fee_type.id}/edit_fee_type']") + |> element("#membership_fee_types tr#mft-#{fee_type.id} td:first-of-type") |> render_click() assert to == "/membership_fee_settings/#{fee_type.id}/edit_fee_type"