From 06ba50f05d6d072226341955a8543ce732f24827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:16:33 +0100 Subject: [PATCH 01/16] Fix translation "Bearbeite" -> "Bearbeiten" --- priv/gettext/de/LC_MESSAGES/default.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index a1bf071..f9d2dcd 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -45,7 +45,7 @@ msgstr "Löschen" #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format msgid "Edit" -msgstr "Bearbeite" +msgstr "Bearbeiten" #: lib/mv_web/live/member_live/show.ex:41 #: lib/mv_web/live/member_live/show.ex:117 From eedd24b93c689fc672868cbc66c41957c8c43f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:16:33 +0100 Subject: [PATCH 02/16] Truncate long entries in tables to prevent height changes --- lib/mv_web/components/core_components.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 54a5a64..3f744c0 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -79,7 +79,7 @@ defmodule MvWeb.CoreComponents do

{msg}

-
@@ -392,14 +392,14 @@ defmodule MvWeb.CoreComponents do {render_slot(col, @row_item.(row))} {if dyn_col[:render] do rendered = dyn_col[:render].(@row_item.(row)) From 125f9ae77b319d159750d76325c5afbdb1bb7873 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 3 Dec 2025 09:55:35 +0100 Subject: [PATCH 03/16] fix: mailto formatting --- lib/mv_web/live/member_live/index.ex | 23 ++++++---- lib/mv_web/live/member_live/index.html.heex | 7 ++- priv/gettext/de/LC_MESSAGES/default.po | 50 ++++++++++----------- priv/gettext/default.pot | 50 ++++++++++----------- priv/gettext/en/LC_MESSAGES/default.po | 50 ++++++++++----------- 5 files changed, 95 insertions(+), 85 deletions(-) diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 3d30d76..67ce522 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -137,13 +137,7 @@ defmodule MvWeb.MemberLive.Index do selected_ids = socket.assigns.selected_members # Filter members that are in the selection and have email addresses - formatted_emails = - socket.assigns.members - |> Enum.filter(fn member -> - MapSet.member?(selected_ids, member.id) && member.email && member.email != "" - end) - |> Enum.map(&format_member_email/1) - + formatted_emails = format_selected_member_emails(socket.assigns.members, selected_ids) email_count = length(formatted_emails) cond do @@ -887,9 +881,20 @@ defmodule MvWeb.MemberLive.Index do end end + # Filters selected members with email addresses and formats them. + # Returns a list of formatted email strings in the format "First Last ". + # Used by both copy_emails and mailto links. + def format_selected_member_emails(members, selected_members) do + members + |> Enum.filter(fn member -> + MapSet.member?(selected_members, member.id) && member.email && member.email != "" + end) + |> Enum.map(&format_member_email/1) + end + # Formats a member's email in the format "First Last " - # Used for copy_emails feature to create email-client-friendly format. - defp format_member_email(member) do + # Used for copy_emails feature and mailto links to create email-client-friendly format. + def format_member_email(member) do first_name = member.first_name || "" last_name = member.last_name || "" diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 58e22b6..9f8851b 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -14,7 +14,12 @@ <.button :if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} - href={"mailto:?bcc=#{@members |> Enum.filter(&(MapSet.member?(@selected_members, &1.id) && &1.email)) |> Enum.map(& &1.email) |> Enum.join(",")}"} + href={ + "mailto:?bcc=" <> + (MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members) + |> Enum.join(", ") + |> URI.encode()) + } aria-label={gettext("Open email program with BCC recipients")} > <.icon name="hero-envelope" /> diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 7a76f62..57df5ab 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -15,7 +15,7 @@ msgstr "" msgid "Actions" msgstr "Aktionen" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -28,19 +28,19 @@ msgid "Attempting to reconnect" msgstr "Verbindung wird wiederhergestellt" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "Stadt" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "Löschen" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -54,7 +54,7 @@ msgid "Edit Member" msgstr "Mitglied bearbeiten" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -70,7 +70,7 @@ msgid "First Name" msgstr "Vorname" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -82,12 +82,12 @@ msgstr "Beitrittsdatum" msgid "Last Name" msgstr "Nachname" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "Neues Mitglied" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -115,7 +115,7 @@ msgid "Exit Date" msgstr "Austrittsdatum" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -130,21 +130,21 @@ msgstr "Notizen" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "Bezahlt" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "Telefonnummer" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -165,7 +165,7 @@ msgid "Saving..." msgstr "Speichern..." #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -176,7 +176,7 @@ msgstr "Straße" msgid "Id" msgstr "ID" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -193,7 +193,7 @@ msgstr "Mitglied anzeigen" msgid "This is a member record from your database." msgstr "Dies ist ein Mitglied aus deiner Datenbank." -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -359,12 +359,12 @@ msgstr "Profil" msgid "Required" msgstr "Erforderlich" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "Alle Mitglieder auswählen" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "Mitglied auswählen" @@ -550,7 +550,7 @@ msgid "Toggle dark mode" msgstr "Dunklen Modus umschalten" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "Suchen..." @@ -566,7 +566,7 @@ msgstr "Benutzer*innen" msgid "Click to sort" msgstr "Klicke um zu sortieren" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format msgid "First name" msgstr "Vorname" @@ -776,7 +776,7 @@ msgstr "Mitglied entverknüpfen" msgid "Unlinking scheduled" msgstr "Entverknüpfung geplant" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -793,27 +793,27 @@ msgstr "E-Mail-Adressen der ausgewählten Mitglieder kopieren" msgid "Copy emails" msgstr "E-Mails kopieren" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "Keine E-Mail-Adressen gefunden" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format msgid "No members selected" msgstr "Keine Mitglieder ausgewählt" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "E-Mail-Programm mit BCC-Empfänger*innen öffnen" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "Im E-Mail-Programm öffnen" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "Tipp: E-Mail-Adressen ins BCC-Feld einfügen für Datenschutzkonformität" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 7229e28..1e0e954 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -16,7 +16,7 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -29,19 +29,19 @@ msgid "Attempting to reconnect" msgstr "" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -55,7 +55,7 @@ msgid "Edit Member" msgstr "" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -71,7 +71,7 @@ msgid "First Name" msgstr "" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -83,12 +83,12 @@ msgstr "" msgid "Last Name" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -116,7 +116,7 @@ msgid "Exit Date" msgstr "" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -131,21 +131,21 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -166,7 +166,7 @@ msgid "Saving..." msgstr "" #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -177,7 +177,7 @@ msgstr "" msgid "Id" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -194,7 +194,7 @@ msgstr "" msgid "This is a member record from your database." msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -360,12 +360,12 @@ msgstr "" msgid "Required" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "" @@ -551,7 +551,7 @@ msgid "Toggle dark mode" msgstr "" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "" @@ -567,7 +567,7 @@ msgstr "" msgid "Click to sort" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format msgid "First name" msgstr "" @@ -777,7 +777,7 @@ msgstr "" msgid "Unlinking scheduled" msgstr "" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -794,27 +794,27 @@ msgstr "" msgid "Copy emails" msgstr "" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format msgid "No members selected" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 3b471d5..319bcc3 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -16,7 +16,7 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -29,19 +29,19 @@ msgid "Attempting to reconnect" msgstr "" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -55,7 +55,7 @@ msgid "Edit Member" msgstr "" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -71,7 +71,7 @@ msgid "First Name" msgstr "" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -83,12 +83,12 @@ msgstr "" msgid "Last Name" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -116,7 +116,7 @@ msgid "Exit Date" msgstr "" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -131,21 +131,21 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -166,7 +166,7 @@ msgid "Saving..." msgstr "" #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -177,7 +177,7 @@ msgstr "" msgid "Id" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -194,7 +194,7 @@ msgstr "" msgid "This is a member record from your database." msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -360,12 +360,12 @@ msgstr "" msgid "Required" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "" @@ -551,7 +551,7 @@ msgid "Toggle dark mode" msgstr "" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "" @@ -567,7 +567,7 @@ msgstr "" msgid "Click to sort" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format, fuzzy msgid "First name" msgstr "" @@ -777,7 +777,7 @@ msgstr "" msgid "Unlinking scheduled" msgstr "" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -794,27 +794,27 @@ msgstr "" msgid "Copy emails" msgstr "" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format, fuzzy msgid "No members selected" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "" From ee094eec2ffb7ea1a10fe4f2d1878957358ff2f5 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 3 Dec 2025 12:36:13 +0100 Subject: [PATCH 04/16] feat: add file env support for secrets --- config/runtime.exs | 99 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 17 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index c50356c..bd48cc9 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -7,6 +7,75 @@ import Config # any compile-time configuration in here, as it won't be applied. # The block below contains prod specific runtime configuration. +# Helper function to read environment variables with Docker secrets support. +# Supports the _FILE suffix pattern: if VAR_FILE is set, reads the value from +# that file path. Otherwise falls back to VAR directly. +# VAR_FILE takes priority and must contain the full absolute path to the secret file. +get_env_or_file = fn var_name, default -> + file_var = "#{var_name}_FILE" + + case System.get_env(file_var) do + nil -> + System.get_env(var_name, default) + + file_path -> + case File.read(file_path) do + {:ok, content} -> + String.trim(content) + + {:error, reason} -> + raise """ + Failed to read secret from file specified in #{file_var}="#{file_path}". + Error: #{inspect(reason)} + """ + end + end +end + +# Same as get_env_or_file but raises if the value is not set +get_env_or_file! = fn var_name, error_message -> + case get_env_or_file.(var_name, nil) do + nil -> raise error_message + value -> value + end +end + +# Build database URL from individual components or use DATABASE_URL directly. +# Supports both approaches: +# 1. DATABASE_URL (or DATABASE_URL_FILE) - full connection URL +# 2. Separate vars: DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD (or _FILE), DATABASE_NAME, DATABASE_PORT +build_database_url = fn -> + case get_env_or_file.("DATABASE_URL", nil) do + nil -> + # Build URL from separate components + host = + System.get_env("DATABASE_HOST") || + raise "DATABASE_HOST is required when DATABASE_URL is not set" + + user = + System.get_env("DATABASE_USER") || + raise "DATABASE_USER is required when DATABASE_URL is not set" + + password = + get_env_or_file!.("DATABASE_PASSWORD", """ + DATABASE_PASSWORD or DATABASE_PASSWORD_FILE is required when DATABASE_URL is not set. + """) + + database = + System.get_env("DATABASE_NAME") || + raise "DATABASE_NAME is required when DATABASE_URL is not set" + + port = System.get_env("DATABASE_PORT", "5432") + + # URL-encode the password to handle special characters + encoded_password = URI.encode_www_form(password) + "ecto://#{user}:#{encoded_password}@#{host}:#{port}/#{database}" + + url -> + url + end +end + # ## Using releases # # If you use `mix release`, you need to explicitly enable the server @@ -21,12 +90,7 @@ if System.get_env("PHX_SERVER") do end if config_env() == :prod do - database_url = - System.get_env("DATABASE_URL") || - raise """ - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - """ + database_url = build_database_url.() maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] @@ -41,12 +105,12 @@ if config_env() == :prod do # want to use a different value for prod and you most likely don't want # to check this value into version control, so we use an environment # variable instead. + # Supports SECRET_KEY_BASE or SECRET_KEY_BASE_FILE for Docker secrets. secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ + get_env_or_file!.("SECRET_KEY_BASE", """ + environment variable SECRET_KEY_BASE (or SECRET_KEY_BASE_FILE) is missing. + You can generate one by calling: mix phx.gen.secret + """) host = System.get_env("PHX_HOST") || raise "Please define the PHX_HOST environment variable." port = String.to_integer(System.get_env("PORT") || "4000") @@ -54,21 +118,22 @@ if config_env() == :prod do config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") # Rauthy OIDC configuration + # Supports OIDC_CLIENT_SECRET or OIDC_CLIENT_SECRET_FILE for Docker secrets. config :mv, :rauthy, client_id: System.get_env("OIDC_CLIENT_ID") || "mv", base_url: System.get_env("OIDC_BASE_URL") || "http://localhost:8080/auth/v1", - client_secret: System.get_env("OIDC_CLIENT_SECRET"), + client_secret: get_env_or_file.("OIDC_CLIENT_SECRET", nil), redirect_uri: System.get_env("OIDC_REDIRECT_URI") || "http://#{host}:#{port}/auth/user/rauthy/callback" # Token signing secret from environment variable # This overrides the placeholder value set in prod.exs + # Supports TOKEN_SIGNING_SECRET or TOKEN_SIGNING_SECRET_FILE for Docker secrets. token_signing_secret = - System.get_env("TOKEN_SIGNING_SECRET") || - raise """ - environment variable TOKEN_SIGNING_SECRET is missing. - You can generate one by calling: mix phx.gen.secret - """ + get_env_or_file!.("TOKEN_SIGNING_SECRET", """ + environment variable TOKEN_SIGNING_SECRET (or TOKEN_SIGNING_SECRET_FILE) is missing. + You can generate one by calling: mix phx.gen.secret + """) config :mv, :token_signing_secret, token_signing_secret From d8384098b48db42a64ad19436bf713a3a6b4e660 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 3 Dec 2025 12:38:24 +0100 Subject: [PATCH 05/16] chore: update prod-compose to use file-envs for secrets --- .gitignore | 3 +++ Justfile | 25 +++++++++++++++++++++- README.md | 2 +- config/runtime.exs | 7 +++--- docker-compose.prod.yml | 47 ++++++++++++++++++++++++++++++----------- 5 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 63ff39e..9517a21 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ npm-debug.log .env .elixir_ls/ + +# Docker secrets directory (generated by `just init-secrets`) +/secrets/ diff --git a/Justfile b/Justfile index 907283f..b97eb14 100644 --- a/Justfile +++ b/Justfile @@ -84,4 +84,27 @@ regen-migrations migration_name commit_hash='': clean: mix clean rm -rf .elixir_ls - rm -rf _build \ No newline at end of file + rm -rf _build + +# Production environment commands +# ================================ + +# Initialize secrets directory with generated secrets (only if not exists) +init-secrets: + #!/usr/bin/env bash + set -euo pipefail + if [ -d "secrets" ]; then + echo "Secrets directory already exists. Skipping generation." + exit 0 + fi + echo "Creating secrets directory and generating secrets..." + mkdir -p secrets + mix phx.gen.secret > secrets/secret_key_base.txt + mix phx.gen.secret > secrets/token_signing_secret.txt + openssl rand -base64 32 | tr -d '\n' > secrets/db_password.txt + touch secrets/oidc_client_secret.txt + echo "Secrets generated in ./secrets/" + +# Start production environment with Docker Compose +start-prod: init-secrets + docker compose -f docker-compose.prod.yml up -d \ No newline at end of file diff --git a/README.md b/README.md index 6db7980..d9569af 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ For actual production deployment: - Set `OIDC_BASE_URL` to your production OIDC provider - Configure proper Docker networks 3. **Set up SSL/TLS** (e.g., via reverse proxy like Nginx/Traefik) -4. **Use secure secrets management** (environment variables, Docker secrets, vault) +4. **Use secure secrets management** — All sensitive environment variables support a `_FILE` suffix for Docker secrets (e.g., `SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base`). See `docker-compose.prod.yml` for an example setup with Docker secrets. 5. **Configure database backups** diff --git a/config/runtime.exs b/config/runtime.exs index bd48cc9..9f41626 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -140,11 +140,10 @@ if config_env() == :prod do config :mv, MvWeb.Endpoint, url: [host: host, port: 443, scheme: "https"], http: [ - # Enable IPv6 and bind on all interfaces. - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # Bind on all IPv4 interfaces. + # Use {0, 0, 0, 0, 0, 0, 0, 0} for IPv6, or {127, 0, 0, 1} for localhost only. # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 - # for details about using IPv6 vs IPv4 and loopback vs public addresses. - ip: {0, 0, 0, 0, 0, 0, 0, 0}, + ip: {0, 0, 0, 0}, port: port ], secret_key_base: secret_key_base, diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 0bb2840..5cac351 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,22 +1,33 @@ services: app: - image: git.local-it.org/local-it/mitgliederverwaltung:latest + image: mitgliederverwaltung:latest container_name: mv-prod-app - # Use host network for local testing to access localhost:8080 (Rauthy) - # In real production, remove this and use external OIDC provider - network_mode: host + ports: + - "4001:4001" environment: - DATABASE_URL: "ecto://postgres:postgres@localhost:5001/mv_prod" - SECRET_KEY_BASE: "${SECRET_KEY_BASE}" - TOKEN_SIGNING_SECRET: "${TOKEN_SIGNING_SECRET}" - PHX_HOST: "${PHX_HOST}" + # Database configuration using separate variables + # Use Docker service name for internal networking + DATABASE_HOST: "db-prod" + DATABASE_PORT: "5432" + DATABASE_USER: "postgres" + DATABASE_NAME: "mv_prod" + DATABASE_PASSWORD_FILE: "/run/secrets/db_password" + # Phoenix secrets via Docker secrets + SECRET_KEY_BASE_FILE: "/run/secrets/secret_key_base" + TOKEN_SIGNING_SECRET_FILE: "/run/secrets/token_signing_secret" + PHX_HOST: "${PHX_HOST:-localhost}" PORT: "4001" PHX_SERVER: "true" - # Rauthy OIDC config - uses localhost because of host network mode + # Rauthy OIDC config - use host.docker.internal to reach host services OIDC_CLIENT_ID: "mv" - OIDC_BASE_URL: "http://localhost:8080/auth/v1" - OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET:-}" + OIDC_BASE_URL: "http://host.docker.internal:8080/auth/v1" + OIDC_CLIENT_SECRET_FILE: "/run/secrets/oidc_client_secret" OIDC_REDIRECT_URI: "http://localhost:4001/auth/user/rauthy/callback" + secrets: + - db_password + - secret_key_base + - token_signing_secret + - oidc_client_secret depends_on: - db-prod restart: unless-stopped @@ -26,13 +37,25 @@ services: container_name: mv-prod-db environment: POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD_FILE: /run/secrets/db_password POSTGRES_DB: mv_prod + secrets: + - db_password volumes: - postgres_data_prod:/var/lib/postgresql/data ports: - "5001:5432" restart: unless-stopped +secrets: + db_password: + file: ./secrets/db_password.txt + secret_key_base: + file: ./secrets/secret_key_base.txt + token_signing_secret: + file: ./secrets/token_signing_secret.txt + oidc_client_secret: + file: ./secrets/oidc_client_secret.txt + volumes: postgres_data_prod: From ce15b8f59b89b3625647fd18b7c66937c2522b91 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 3 Dec 2025 09:55:35 +0100 Subject: [PATCH 06/16] fix: mailto formatting --- lib/mv_web/live/member_live/index.ex | 23 ++++++---- lib/mv_web/live/member_live/index.html.heex | 7 ++- priv/gettext/de/LC_MESSAGES/default.po | 50 ++++++++++----------- priv/gettext/default.pot | 50 ++++++++++----------- priv/gettext/en/LC_MESSAGES/default.po | 50 ++++++++++----------- 5 files changed, 95 insertions(+), 85 deletions(-) diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 3d30d76..67ce522 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -137,13 +137,7 @@ defmodule MvWeb.MemberLive.Index do selected_ids = socket.assigns.selected_members # Filter members that are in the selection and have email addresses - formatted_emails = - socket.assigns.members - |> Enum.filter(fn member -> - MapSet.member?(selected_ids, member.id) && member.email && member.email != "" - end) - |> Enum.map(&format_member_email/1) - + formatted_emails = format_selected_member_emails(socket.assigns.members, selected_ids) email_count = length(formatted_emails) cond do @@ -887,9 +881,20 @@ defmodule MvWeb.MemberLive.Index do end end + # Filters selected members with email addresses and formats them. + # Returns a list of formatted email strings in the format "First Last ". + # Used by both copy_emails and mailto links. + def format_selected_member_emails(members, selected_members) do + members + |> Enum.filter(fn member -> + MapSet.member?(selected_members, member.id) && member.email && member.email != "" + end) + |> Enum.map(&format_member_email/1) + end + # Formats a member's email in the format "First Last " - # Used for copy_emails feature to create email-client-friendly format. - defp format_member_email(member) do + # Used for copy_emails feature and mailto links to create email-client-friendly format. + def format_member_email(member) do first_name = member.first_name || "" last_name = member.last_name || "" diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 58e22b6..9f8851b 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -14,7 +14,12 @@ <.button :if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} - href={"mailto:?bcc=#{@members |> Enum.filter(&(MapSet.member?(@selected_members, &1.id) && &1.email)) |> Enum.map(& &1.email) |> Enum.join(",")}"} + href={ + "mailto:?bcc=" <> + (MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members) + |> Enum.join(", ") + |> URI.encode()) + } aria-label={gettext("Open email program with BCC recipients")} > <.icon name="hero-envelope" /> diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 7a76f62..57df5ab 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -15,7 +15,7 @@ msgstr "" msgid "Actions" msgstr "Aktionen" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -28,19 +28,19 @@ msgid "Attempting to reconnect" msgstr "Verbindung wird wiederhergestellt" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "Stadt" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "Löschen" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -54,7 +54,7 @@ msgid "Edit Member" msgstr "Mitglied bearbeiten" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -70,7 +70,7 @@ msgid "First Name" msgstr "Vorname" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -82,12 +82,12 @@ msgstr "Beitrittsdatum" msgid "Last Name" msgstr "Nachname" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "Neues Mitglied" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -115,7 +115,7 @@ msgid "Exit Date" msgstr "Austrittsdatum" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -130,21 +130,21 @@ msgstr "Notizen" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "Bezahlt" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "Telefonnummer" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -165,7 +165,7 @@ msgid "Saving..." msgstr "Speichern..." #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -176,7 +176,7 @@ msgstr "Straße" msgid "Id" msgstr "ID" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -193,7 +193,7 @@ msgstr "Mitglied anzeigen" msgid "This is a member record from your database." msgstr "Dies ist ein Mitglied aus deiner Datenbank." -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -359,12 +359,12 @@ msgstr "Profil" msgid "Required" msgstr "Erforderlich" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "Alle Mitglieder auswählen" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "Mitglied auswählen" @@ -550,7 +550,7 @@ msgid "Toggle dark mode" msgstr "Dunklen Modus umschalten" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "Suchen..." @@ -566,7 +566,7 @@ msgstr "Benutzer*innen" msgid "Click to sort" msgstr "Klicke um zu sortieren" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format msgid "First name" msgstr "Vorname" @@ -776,7 +776,7 @@ msgstr "Mitglied entverknüpfen" msgid "Unlinking scheduled" msgstr "Entverknüpfung geplant" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -793,27 +793,27 @@ msgstr "E-Mail-Adressen der ausgewählten Mitglieder kopieren" msgid "Copy emails" msgstr "E-Mails kopieren" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "Keine E-Mail-Adressen gefunden" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format msgid "No members selected" msgstr "Keine Mitglieder ausgewählt" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "E-Mail-Programm mit BCC-Empfänger*innen öffnen" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "Im E-Mail-Programm öffnen" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "Tipp: E-Mail-Adressen ins BCC-Feld einfügen für Datenschutzkonformität" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 7229e28..1e0e954 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -16,7 +16,7 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -29,19 +29,19 @@ msgid "Attempting to reconnect" msgstr "" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -55,7 +55,7 @@ msgid "Edit Member" msgstr "" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -71,7 +71,7 @@ msgid "First Name" msgstr "" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -83,12 +83,12 @@ msgstr "" msgid "Last Name" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -116,7 +116,7 @@ msgid "Exit Date" msgstr "" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -131,21 +131,21 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -166,7 +166,7 @@ msgid "Saving..." msgstr "" #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -177,7 +177,7 @@ msgstr "" msgid "Id" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -194,7 +194,7 @@ msgstr "" msgid "This is a member record from your database." msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -360,12 +360,12 @@ msgstr "" msgid "Required" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "" @@ -551,7 +551,7 @@ msgid "Toggle dark mode" msgstr "" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "" @@ -567,7 +567,7 @@ msgstr "" msgid "Click to sort" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format msgid "First name" msgstr "" @@ -777,7 +777,7 @@ msgstr "" msgid "Unlinking scheduled" msgstr "" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -794,27 +794,27 @@ msgstr "" msgid "Copy emails" msgstr "" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format msgid "No members selected" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 3b471d5..319bcc3 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -16,7 +16,7 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:243 +#: lib/mv_web/live/member_live/index.html.heex:248 #: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" @@ -29,19 +29,19 @@ msgid "Attempting to reconnect" msgstr "" #: lib/mv_web/live/member_live/form.ex:53 -#: lib/mv_web/live/member_live/index.html.heex:179 +#: lib/mv_web/live/member_live/index.html.heex:184 #: lib/mv_web/live/member_live/show.ex:58 #, elixir-autogen, elixir-format msgid "City" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:245 +#: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:237 +#: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format @@ -55,7 +55,7 @@ msgid "Edit Member" msgstr "" #: lib/mv_web/live/member_live/form.ex:47 -#: lib/mv_web/live/member_live/index.html.heex:107 +#: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 #: lib/mv_web/live/user_live/form.ex:46 #: lib/mv_web/live/user_live/index.html.heex:44 @@ -71,7 +71,7 @@ msgid "First Name" msgstr "" #: lib/mv_web/live/member_live/form.ex:50 -#: lib/mv_web/live/member_live/index.html.heex:215 +#: lib/mv_web/live/member_live/index.html.heex:220 #: lib/mv_web/live/member_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Join Date" @@ -83,12 +83,12 @@ msgstr "" msgid "Last Name" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:24 +#: lib/mv_web/live/member_live/index.html.heex:29 #, elixir-autogen, elixir-format msgid "New Member" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:234 +#: lib/mv_web/live/member_live/index.html.heex:239 #: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" @@ -116,7 +116,7 @@ msgid "Exit Date" msgstr "" #: lib/mv_web/live/member_live/form.ex:55 -#: lib/mv_web/live/member_live/index.html.heex:143 +#: lib/mv_web/live/member_live/index.html.heex:148 #: lib/mv_web/live/member_live/show.ex:60 #, elixir-autogen, elixir-format msgid "House Number" @@ -131,21 +131,21 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 #: lib/mv_web/live/member_live/form.ex:48 -#: lib/mv_web/live/member_live/index.html.heex:224 +#: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 #, elixir-autogen, elixir-format msgid "Paid" msgstr "" #: lib/mv_web/live/member_live/form.ex:49 -#: lib/mv_web/live/member_live/index.html.heex:197 +#: lib/mv_web/live/member_live/index.html.heex:202 #: lib/mv_web/live/member_live/show.ex:54 #, elixir-autogen, elixir-format msgid "Phone Number" msgstr "" #: lib/mv_web/live/member_live/form.ex:56 -#: lib/mv_web/live/member_live/index.html.heex:161 +#: lib/mv_web/live/member_live/index.html.heex:166 #: lib/mv_web/live/member_live/show.ex:61 #, elixir-autogen, elixir-format msgid "Postal Code" @@ -166,7 +166,7 @@ msgid "Saving..." msgstr "" #: lib/mv_web/live/member_live/form.ex:54 -#: lib/mv_web/live/member_live/index.html.heex:125 +#: lib/mv_web/live/member_live/index.html.heex:130 #: lib/mv_web/live/member_live/show.ex:59 #, elixir-autogen, elixir-format msgid "Street" @@ -177,7 +177,7 @@ msgstr "" msgid "Id" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:61 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -194,7 +194,7 @@ msgstr "" msgid "This is a member record from your database." msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:229 +#: lib/mv_web/live/member_live/index.html.heex:234 #: lib/mv_web/live/member_live/index/formatter.ex:60 #: lib/mv_web/live/member_live/show.ex:52 #, elixir-autogen, elixir-format @@ -360,12 +360,12 @@ msgstr "" msgid "Required" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:63 +#: lib/mv_web/live/member_live/index.html.heex:68 #, elixir-autogen, elixir-format msgid "Select all members" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:77 +#: lib/mv_web/live/member_live/index.html.heex:82 #, elixir-autogen, elixir-format msgid "Select member" msgstr "" @@ -551,7 +551,7 @@ msgid "Toggle dark mode" msgstr "" #: lib/mv_web/live/components/search_bar_component.ex:15 -#: lib/mv_web/live/member_live/index.html.heex:34 +#: lib/mv_web/live/member_live/index.html.heex:39 #, elixir-autogen, elixir-format msgid "Search..." msgstr "" @@ -567,7 +567,7 @@ msgstr "" msgid "Click to sort" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:89 +#: lib/mv_web/live/member_live/index.html.heex:94 #, elixir-autogen, elixir-format, fuzzy msgid "First name" msgstr "" @@ -777,7 +777,7 @@ msgstr "" msgid "Unlinking scheduled" msgstr "" -#: lib/mv_web/live/member_live/index.ex:165 +#: lib/mv_web/live/member_live/index.ex:159 #, elixir-autogen, elixir-format msgid "Copied %{count} email address to clipboard" msgid_plural "Copied %{count} email addresses to clipboard" @@ -794,27 +794,27 @@ msgstr "" msgid "Copy emails" msgstr "" -#: lib/mv_web/live/member_live/index.ex:154 +#: lib/mv_web/live/member_live/index.ex:148 #, elixir-autogen, elixir-format msgid "No email addresses found" msgstr "" -#: lib/mv_web/live/member_live/index.ex:151 +#: lib/mv_web/live/member_live/index.ex:145 #, elixir-autogen, elixir-format, fuzzy msgid "No members selected" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:18 +#: lib/mv_web/live/member_live/index.html.heex:23 #, elixir-autogen, elixir-format msgid "Open email program with BCC recipients" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex:21 +#: lib/mv_web/live/member_live/index.html.heex:26 #, elixir-autogen, elixir-format msgid "Open in email program" msgstr "" -#: lib/mv_web/live/member_live/index.ex:174 +#: lib/mv_web/live/member_live/index.ex:168 #, elixir-autogen, elixir-format msgid "Tip: Paste email addresses into the BCC field for privacy compliance" msgstr "" From 09c75212b2c431864ac4350f5fbce6a6d7737480 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 3 Dec 2025 13:46:55 +0100 Subject: [PATCH 07/16] chore: add remove-gettext-conflicts to Justfile --- Justfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 907283f..a91c0e4 100644 --- a/Justfile +++ b/Justfile @@ -84,4 +84,10 @@ regen-migrations migration_name commit_hash='': clean: mix clean rm -rf .elixir_ls - rm -rf _build \ No newline at end of file + rm -rf _build + +# Remove Git merge conflict markers from gettext files +remove-gettext-conflicts: + #!/usr/bin/env bash + set -euo pipefail + find priv/gettext -type f -exec sed -i '/^<<<<<<< HEAD$/d; /^=======$/d; /^>>>>>>>/d' {} \; \ No newline at end of file From 366d4c104a7378f8ec475451e77144c1e71ddd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:16:33 +0100 Subject: [PATCH 08/16] Prevent tables from growing the page horizontally --- lib/mv_web/components/core_components.ex | 108 +++++++++--------- .../live/components/sort_header_component.ex | 2 +- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 3f744c0..08133b5 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -368,61 +368,63 @@ defmodule MvWeb.CoreComponents do end ~H""" - - - - - - - - - - - - + + + +
{col[:label]} - <.live_component - module={MvWeb.Components.SortHeaderComponent} - id={:"sort_custom_field_#{dyn_col[:custom_field].id}"} - field={"custom_field_#{dyn_col[:custom_field].id}"} - label={dyn_col[:custom_field].name} - sort_field={@sort_field} - sort_order={@sort_order} - /> - - {gettext("Actions")} -
- {render_slot(col, @row_item.(row))} - - {if dyn_col[:render] do - rendered = dyn_col[:render].(@row_item.(row)) +
+ + + + + + + + + + + + - - - -
{col[:label]} + <.live_component + module={MvWeb.Components.SortHeaderComponent} + id={:"sort_custom_field_#{dyn_col[:custom_field].id}"} + field={"custom_field_#{dyn_col[:custom_field].id}"} + label={dyn_col[:custom_field].name} + sort_field={@sort_field} + sort_order={@sort_order} + /> + + {gettext("Actions")} +
+ {render_slot(col, @row_item.(row))} + + {if dyn_col[:render] do + rendered = dyn_col[:render].(@row_item.(row)) - if rendered == "" do - "" + if rendered == "" do + "" + else + rendered + end else - rendered - end - else - "" - end} - -
- <%= for action <- @action do %> - {render_slot(action, @row_item.(row))} - <% end %> -
-
+ "" + end} +
+
+ <%= for action <- @action do %> + {render_slot(action, @row_item.(row))} + <% end %> +
+
+ """ end diff --git a/lib/mv_web/live/components/sort_header_component.ex b/lib/mv_web/live/components/sort_header_component.ex index b847308..3817d90 100644 --- a/lib/mv_web/live/components/sort_header_component.ex +++ b/lib/mv_web/live/components/sort_header_component.ex @@ -19,7 +19,7 @@ defmodule MvWeb.Components.SortHeaderComponent do @impl true def render(assigns) do ~H""" -
+
+ + +
+ + <%!-- Periods Table --%> +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + {gettext("Time Period")}{gettext("Interval")}{gettext("Amount")}{gettext("Status")}{gettext("Notes")}{gettext("Actions")}
+ + +
+ {period.period_start} – {period.period_end} +
+
+ {gettext("Current")} +
+
+ {format_interval(period.interval)} + + {format_currency(period.amount)} + + <.status_badge status={period.status} /> + + + {period.notes} + + + +
+ <.link + href="#" + class={[ + "cursor-not-allowed", + if(period.status == :paid, do: "invisible", else: "opacity-50") + ]} + > + {gettext("Paid")} + + <.link + href="#" + class={[ + "cursor-not-allowed", + if(period.status == :suspended, do: "invisible", else: "opacity-50") + ]} + > + {gettext("Suspend")} + + <.link + href="#" + class={[ + "cursor-not-allowed", + if(period.status != :paid, do: "invisible", else: "opacity-50") + ]} + > + {gettext("Reopen")} + + <.link href="#" class="opacity-50 cursor-not-allowed"> + {gettext("Note")} + +
+
+
+ + """ + end + + # Mock-up warning banner component - subtle orange style + defp mockup_warning(assigns) do + ~H""" +
+ <.icon name="hero-exclamation-triangle" class="size-5 shrink-0" /> +
+ {gettext("Preview Mockup")} + + – {gettext("This page is not functional and only displays the planned features.")} + +
+
+ """ + end + + # Status badge component + attr :status, :atom, required: true + + defp status_badge(%{status: :paid} = assigns) do + ~H""" + + <.icon name="hero-check-circle-mini" class="size-3" /> + {gettext("Paid")} + + """ + end + + defp status_badge(%{status: :unpaid} = assigns) do + ~H""" + + <.icon name="hero-x-circle-mini" class="size-3" /> + {gettext("Unpaid")} + + """ + end + + defp status_badge(%{status: :suspended} = assigns) do + ~H""" + + <.icon name="hero-pause-circle-mini" class="size-3" /> + {gettext("Suspended")} + + """ + end + + defp period_row_class(:unpaid), do: "bg-error/5" + defp period_row_class(:suspended), do: "bg-base-200/50" + defp period_row_class(_), do: "" + + # Mock member data + defp mock_member do + %{ + id: "123", + first_name: "Maria", + last_name: "Weber", + email: "maria.weber@example.de", + contribution_type: gettext("Regular"), + joined_at: "15.03.2021", + contribution_start: "01.01.2021" + } + end + + # Mock periods data + defp mock_periods do + [ + %{ + id: "p1", + period_start: "01.01.2025", + period_end: "31.12.2025", + interval: :yearly, + amount: Decimal.new("60.00"), + status: :unpaid, + notes: nil, + is_current: true + }, + %{ + id: "p2", + period_start: "01.01.2024", + period_end: "31.12.2024", + interval: :yearly, + amount: Decimal.new("60.00"), + status: :paid, + notes: gettext("Paid via bank transfer"), + is_current: false + }, + %{ + id: "p3", + period_start: "01.01.2023", + period_end: "31.12.2023", + interval: :yearly, + amount: Decimal.new("50.00"), + status: :paid, + notes: nil, + is_current: false + }, + %{ + id: "p4", + period_start: "01.01.2022", + period_end: "31.12.2022", + interval: :yearly, + amount: Decimal.new("50.00"), + status: :paid, + notes: nil, + is_current: false + }, + %{ + id: "p5", + period_start: "01.01.2021", + period_end: "31.12.2021", + interval: :yearly, + amount: Decimal.new("50.00"), + status: :suspended, + notes: gettext("Joining year - reduced to 0"), + is_current: false + } + ] + end + + defp format_currency(%Decimal{} = amount) do + "#{Decimal.to_string(amount)} €" + end + + defp format_interval(:monthly), do: gettext("Monthly") + defp format_interval(:quarterly), do: gettext("Quarterly") + defp format_interval(:half_yearly), do: gettext("Half-yearly") + defp format_interval(:yearly), do: gettext("Yearly") +end diff --git a/lib/mv_web/live/contribution_settings_live.ex b/lib/mv_web/live/contribution_settings_live.ex new file mode 100644 index 0000000..713bc8c --- /dev/null +++ b/lib/mv_web/live/contribution_settings_live.ex @@ -0,0 +1,277 @@ +defmodule MvWeb.ContributionSettingsLive do + @moduledoc """ + Mock-up LiveView for Contribution Settings (Admin). + + This is a preview-only page that displays the planned UI for managing + global contribution settings. It shows static mock data and is not functional. + + ## Planned Features (Future Implementation) + - Set default contribution type for new members + - Configure whether joining period is included in contributions + - Explanatory text with examples + + ## Settings + - `default_contribution_type_id` - UUID of the default contribution type + - `include_joining_period` - Boolean whether to include joining period + + ## Note + This page is intentionally non-functional and serves as a UI mockup + for the upcoming Membership Contributions feature. + """ + use MvWeb, :live_view + + @impl true + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:page_title, gettext("Contribution Settings")) + |> assign(:contribution_types, mock_contribution_types()) + |> assign(:selected_type_id, "1") + |> assign(:include_joining_period, true)} + end + + @impl true + def render(assigns) do + ~H""" + + <.mockup_warning /> + + <.header> + {gettext("Contribution Settings")} + <:subtitle> + {gettext("Configure global settings for membership contributions.")} + + + +
+ <%!-- Settings Form --%> +
+
+

+ <.icon name="hero-cog-6-tooth" class="size-5" /> + {gettext("Global Settings")} +

+ +
+ <%!-- Default Contribution Type --%> +
+ + +

+ {gettext( + "This contribution type is automatically assigned to all new members. Can be changed individually per member." + )} +

+
+ + <%!-- Include Joining Period --%> +
+ +
+

+ {gettext("When active: Members pay from the period of their joining.")} +

+

+ {gettext("When inactive: Members pay from the next full period after joining.")} +

+
+
+ +
+ + +
+
+
+ + <%!-- Examples Card --%> +
+
+

+ <.icon name="hero-light-bulb" class="size-5" /> + {gettext("Examples")} +

+ + <.example_section + title={gettext("Yearly Interval - Joining Period 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 Period 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 Period 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 Period 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")} + /> +
+
+
+ + <.example_member_card /> +
+ """ + end + + # Example member card with link to period view + defp example_member_card(assigns) do + ~H""" +
+
+

+ <.icon name="hero-user" class="size-5" /> + {gettext("Example: Member Contribution View")} +

+

+ {gettext( + "See how the contribution periods will be displayed for an individual member. This example shows Maria Weber with multiple contribution periods." + )} +

+
+ <.link navigate={~p"/contributions/member/example"} class="btn btn-primary btn-sm"> + <.icon name="hero-eye" class="size-4" /> + {gettext("View Example Member")} + +
+
+
+ """ + end + + # Mock-up warning banner component - subtle orange style + defp mockup_warning(assigns) do + ~H""" +
+ <.icon name="hero-exclamation-triangle" class="size-5 shrink-0" /> +
+ {gettext("Preview Mockup")} + + – {gettext("This page is not functional and only displays the planned features.")} + +
+
+ """ + end + + # Example section component + attr :title, :string, required: true + attr :joining_date, :string, required: true + attr :include_joining, :boolean, required: true + attr :start_date, :string, required: true + attr :periods, :list, required: true + attr :note, :string, required: true + + defp example_section(assigns) do + ~H""" +
+

{@title}

+
+

+ {gettext("Joining date")}: + {@joining_date} +

+

+ {gettext("Contribution start")}: + {@start_date} +

+

+ {gettext("Generated periods")}: + + {Enum.join(@periods, ", ")} + +

+
+

→ {@note}

+
+ """ + end + + # Mock data for demonstration + defp mock_contribution_types do + [ + %{ + id: "1", + name: gettext("Regular"), + amount: Decimal.new("60.00"), + interval: :yearly + }, + %{ + id: "2", + name: gettext("Reduced"), + amount: Decimal.new("30.00"), + interval: :yearly + }, + %{ + id: "3", + name: gettext("Student"), + amount: Decimal.new("5.00"), + interval: :monthly + }, + %{ + id: "4", + name: gettext("Family"), + amount: Decimal.new("25.00"), + interval: :quarterly + } + ] + end + + defp format_currency(%Decimal{} = amount) do + "#{Decimal.to_string(amount)} €" + end + + defp format_interval(:monthly), do: gettext("Monthly") + defp format_interval(:quarterly), do: gettext("Quarterly") + defp format_interval(:half_yearly), do: gettext("Half-yearly") + defp format_interval(:yearly), do: gettext("Yearly") +end diff --git a/lib/mv_web/live/contribution_type_live/index.ex b/lib/mv_web/live/contribution_type_live/index.ex new file mode 100644 index 0000000..9a7b602 --- /dev/null +++ b/lib/mv_web/live/contribution_type_live/index.ex @@ -0,0 +1,205 @@ +defmodule MvWeb.ContributionTypeLive.Index do + @moduledoc """ + Mock-up LiveView for Contribution Types Management (Admin). + + This is a preview-only page that displays the planned UI for managing + contribution types. It shows static mock data and is not functional. + + ## Planned Features (Future Implementation) + - List all contribution types + - Display: Name, Amount, Interval, Member count + - Create new contribution types + - Edit existing contribution types (name, amount, description - NOT interval) + - Delete contribution types (if no members assigned) + + ## Note + This page is intentionally non-functional and serves as a UI mockup + for the upcoming Membership Contributions feature. + """ + use MvWeb, :live_view + + @impl true + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:page_title, gettext("Contribution Types")) + |> assign(:contribution_types, mock_contribution_types())} + end + + @impl true + def render(assigns) do + ~H""" + + <.mockup_warning /> + + <.header> + {gettext("Contribution Types")} + <:subtitle> + {gettext("Manage contribution types for membership fees.")} + + <:actions> + + + + + <.table id="contribution_types" rows={@contribution_types} row_id={fn ct -> "ct-#{ct.id}" end}> + <:col :let={ct} label={gettext("Name")}> + {ct.name} +

{ct.description}

+ + + <:col :let={ct} label={gettext("Amount")}> + {format_currency(ct.amount)} + + + <:col :let={ct} label={gettext("Interval")}> + {format_interval(ct.interval)} + + + <:col :let={ct} label={gettext("Members")}> + {ct.member_count} + + + <:action :let={_ct}> + + + + <:action :let={ct}> + + + + + <.info_card /> +
+ """ + end + + # Mock-up warning banner component - subtle orange style + defp mockup_warning(assigns) do + ~H""" +
+ <.icon name="hero-exclamation-triangle" class="size-5 shrink-0" /> +
+ {gettext("Preview Mockup")} + + – {gettext("This page is not functional and only displays the planned features.")} + +
+
+ """ + end + + # Info card explaining the contribution type concept + defp info_card(assigns) do + ~H""" +
+
+

+ <.icon name="hero-information-circle" class="size-5" /> + {gettext("About Contribution Types")} +

+
+

+ {gettext( + "Contribution 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.")} +
  • +
+
+
+
+ """ + end + + # Mock data for demonstration + defp mock_contribution_types do + [ + %{ + id: "1", + name: gettext("Regular"), + description: gettext("Standard membership fee for regular members"), + amount: Decimal.new("60.00"), + interval: :yearly, + member_count: 45 + }, + %{ + id: "2", + name: gettext("Reduced"), + description: gettext("Reduced fee for unemployed, pensioners, or low income"), + amount: Decimal.new("30.00"), + interval: :yearly, + member_count: 12 + }, + %{ + id: "3", + name: gettext("Student"), + description: gettext("Monthly fee for students and trainees"), + amount: Decimal.new("5.00"), + interval: :monthly, + member_count: 8 + }, + %{ + id: "4", + name: gettext("Family"), + description: gettext("Quarterly fee for family memberships"), + amount: Decimal.new("25.00"), + interval: :quarterly, + member_count: 15 + }, + %{ + id: "5", + name: gettext("Supporting Member"), + description: gettext("Half-yearly contribution for supporting members"), + amount: Decimal.new("100.00"), + interval: :half_yearly, + member_count: 3 + }, + %{ + id: "6", + name: gettext("Honorary"), + description: gettext("No fee for honorary members"), + amount: Decimal.new("0.00"), + interval: :yearly, + member_count: 2 + } + ] + end + + defp format_currency(%Decimal{} = amount) do + "#{Decimal.to_string(amount)} €" + end + + defp format_interval(:monthly), do: gettext("Monthly") + defp format_interval(:quarterly), do: gettext("Quarterly") + defp format_interval(:half_yearly), do: gettext("Half-yearly") + defp format_interval(:yearly), do: gettext("Yearly") +end diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex index 09a2792..c574e17 100644 --- a/lib/mv_web/router.ex +++ b/lib/mv_web/router.ex @@ -75,6 +75,11 @@ defmodule MvWeb.Router do live "/settings", GlobalSettingsLive + # Contribution Management (Mock-ups) + live "/contribution_types", ContributionTypeLive.Index, :index + live "/contribution_settings", ContributionSettingsLive + live "/contributions/member/:id", ContributionPeriodLive.Show, :show + post "/set_locale", LocaleController, :set_locale end diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 66bcec1..be7ffef 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -11,6 +11,7 @@ msgstr "" "Language: en\n" #: lib/mv_web/components/core_components.ex:386 +#: lib/mv_web/live/contribution_period_live/show.ex:141 #, elixir-autogen, elixir-format msgid "Actions" msgstr "Aktionen" @@ -34,12 +35,14 @@ msgstr "Verbindung wird wiederhergestellt" msgid "City" msgstr "Stadt" +#: lib/mv_web/live/contribution_type_live/index.ex:78 #: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "Löschen" +#: lib/mv_web/live/contribution_type_live/index.ex:66 #: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 @@ -53,6 +56,7 @@ msgstr "Bearbeiten" msgid "Edit Member" msgstr "Mitglied bearbeiten" +#: lib/mv_web/live/contribution_period_live/show.ex:58 #: lib/mv_web/live/member_live/form.ex:47 #: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 @@ -121,6 +125,7 @@ msgstr "Austrittsdatum" msgid "House Number" msgstr "Hausnummer" +#: lib/mv_web/live/contribution_period_live/show.ex:140 #: lib/mv_web/live/member_live/form.ex:52 #: lib/mv_web/live/member_live/show.ex:57 #, elixir-autogen, elixir-format @@ -129,6 +134,8 @@ msgstr "Notizen" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 +#: lib/mv_web/live/contribution_period_live/show.ex:186 +#: lib/mv_web/live/contribution_period_live/show.ex:242 #: lib/mv_web/live/member_live/form.ex:48 #: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 @@ -288,7 +295,7 @@ msgstr "ID" msgid "Immutable" msgstr "Unveränderlich" -#: lib/mv_web/components/layouts/navbar.ex:102 +#: lib/mv_web/components/layouts/navbar.ex:113 #, elixir-autogen, elixir-format msgid "Logout" msgstr "Abmelden" @@ -305,12 +312,14 @@ msgid "Member" msgstr "Mitglied" #: lib/mv_web/components/layouts/navbar.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:61 #: lib/mv_web/live/member_live/index.ex:73 #: lib/mv_web/live/member_live/index.html.heex:3 #, elixir-autogen, elixir-format msgid "Members" msgstr "Mitglieder" +#: lib/mv_web/live/contribution_type_live/index.ex:48 #: lib/mv_web/live/custom_field_live/form.ex:51 #, elixir-autogen, elixir-format msgid "Name" @@ -331,6 +340,7 @@ msgstr "Nicht aktiviert" msgid "Not set" msgstr "Nicht gesetzt" +#: lib/mv_web/live/contribution_period_live/show.ex:207 #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 #: lib/mv_web/live/user_live/form.ex:224 @@ -349,7 +359,7 @@ msgstr "OIDC ID" msgid "Password Authentication" msgstr "Passwort-Authentifizierung" -#: lib/mv_web/components/layouts/navbar.ex:95 +#: lib/mv_web/components/layouts/navbar.ex:106 #, elixir-autogen, elixir-format msgid "Profil" msgstr "Profil" @@ -369,7 +379,7 @@ msgstr "Alle Mitglieder auswählen" msgid "Select member" msgstr "Mitglied auswählen" -#: lib/mv_web/components/layouts/navbar.ex:99 +#: lib/mv_web/components/layouts/navbar.ex:110 #, elixir-autogen, elixir-format msgid "Settings" msgstr "Einstellungen" @@ -537,14 +547,14 @@ msgstr "Zurück zur Mitgliederliste" msgid "Back to users list" msgstr "Zurück zur Benutzer*innen-Liste" -#: lib/mv_web/components/layouts/navbar.ex:33 -#: lib/mv_web/components/layouts/navbar.ex:39 +#: lib/mv_web/components/layouts/navbar.ex:44 +#: lib/mv_web/components/layouts/navbar.ex:50 #, elixir-autogen, elixir-format msgid "Select language" msgstr "Sprache auswählen" -#: lib/mv_web/components/layouts/navbar.ex:46 -#: lib/mv_web/components/layouts/navbar.ex:66 +#: lib/mv_web/components/layouts/navbar.ex:57 +#: lib/mv_web/components/layouts/navbar.ex:77 #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr "Dunklen Modus umschalten" @@ -716,6 +726,7 @@ msgstr "Vereinsdaten" msgid "Manage global settings for the association." msgstr "Passe übergreifende Einstellungen für den Verein an." +#: lib/mv_web/live/contribution_settings_live.ex:102 #: lib/mv_web/live/global_settings_live.ex:56 #, elixir-autogen, elixir-format, fuzzy msgid "Save Settings" @@ -853,6 +864,432 @@ msgstr "Nicht bezahlt" msgid "Payment filter" msgstr "Zahlungsfilter" +#: lib/mv_web/live/contribution_period_live/show.ex:107 +#, elixir-autogen, elixir-format +msgid "%{count} period selected" +msgid_plural "%{count} periods selected" +msgstr[0] "%{count} Beiträge ausgewählt" +msgstr[1] "%{count} Beiträge ausgewählt" + +#: lib/mv_web/live/contribution_type_live/index.ex:113 +#, elixir-autogen, elixir-format +msgid "About Contribution Types" +msgstr "Über Beitragsarten" + +#: lib/mv_web/live/contribution_period_live/show.ex:138 +#: lib/mv_web/live/contribution_type_live/index.ex:53 +#, elixir-autogen, elixir-format +msgid "Amount" +msgstr "Betrag" + +#: lib/mv_web/live/contribution_period_live/show.ex:48 +#, elixir-autogen, elixir-format +msgid "Back to Settings" +msgstr "Zurück zu Einstellungen" + +#: lib/mv_web/live/contribution_type_live/index.ex:124 +#, elixir-autogen, elixir-format +msgid "Can be changed at any time. Amount changes affect future periods only." +msgstr "Kann jederzeit geändert werden. Betragsänderungen wirken sich nur auf zukünftige Beiträge aus." + +#: lib/mv_web/live/contribution_type_live/index.ex:77 +#, elixir-autogen, elixir-format +msgid "Cannot delete - members assigned" +msgstr "Löschen nicht möglich - Mitglieder zugewiesen" + +#: lib/mv_web/live/contribution_period_live/show.ex:83 +#, elixir-autogen, elixir-format +msgid "Change Contribution Type" +msgstr "Beitragsart ändern" + +#: lib/mv_web/live/contribution_settings_live.ex:42 +#, elixir-autogen, elixir-format +msgid "Configure global settings for membership contributions." +msgstr "Globale Einstellungen für Mitgliedsbeiträge konfigurieren." + +#: lib/mv_web/components/layouts/navbar.ex:34 +#: lib/mv_web/live/contribution_settings_live.ex:27 +#: lib/mv_web/live/contribution_settings_live.ex:40 +#, elixir-autogen, elixir-format +msgid "Contribution Settings" +msgstr "Beitragseinstellungen" + +#: lib/mv_web/live/contribution_period_live/show.ex:62 +#, elixir-autogen, elixir-format +msgid "Contribution Start" +msgstr "Beitragsbeginn" + +#: lib/mv_web/components/layouts/navbar.ex:32 +#: lib/mv_web/live/contribution_type_live/index.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:36 +#, elixir-autogen, elixir-format +msgid "Contribution Types" +msgstr "Beitragsarten" + +#: lib/mv_web/live/contribution_settings_live.ex:224 +#, elixir-autogen, elixir-format +msgid "Contribution start" +msgstr "Beitragsbeginn" + +#: lib/mv_web/live/contribution_period_live/show.ex:41 +#, elixir-autogen, elixir-format +msgid "Contribution type" +msgstr "Beitragsart" + +#: lib/mv_web/live/contribution_type_live/index.ex:117 +#, elixir-autogen, elixir-format +msgid "Contribution types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation." +msgstr "Beitragsarten definieren verschiedene Mitgliedsbeitragsstrukturen. Jede Art hat ein festes Intervall (monatlich, quartalsweise, halbjährlich, jährlich), das nach der Erstellung nicht mehr geändert werden kann." + +#: lib/mv_web/components/layouts/navbar.ex:30 +#, elixir-autogen, elixir-format +msgid "Contributions" +msgstr "Beiträge" + +#: lib/mv_web/live/contribution_period_live/show.ex:39 +#, elixir-autogen, elixir-format +msgid "Contributions for %{name}" +msgstr "Beiträge für %{name}" + +#: lib/mv_web/live/contribution_period_live/show.ex:159 +#, elixir-autogen, elixir-format +msgid "Current" +msgstr "Aktuell" + +#: lib/mv_web/live/contribution_settings_live.ex:60 +#, elixir-autogen, elixir-format +msgid "Default Contribution Type" +msgstr "Standard-Beitragsart" + +#: lib/mv_web/live/contribution_type_live/index.ex:133 +#, elixir-autogen, elixir-format +msgid "Deletion" +msgstr "Löschung" + +#: lib/mv_web/live/contribution_settings_live.ex:173 +#, elixir-autogen, elixir-format +msgid "Example: Member Contribution View" +msgstr "Beispiel: Mitglieder-Beitragsansicht" + +#: lib/mv_web/live/contribution_settings_live.ex:113 +#, elixir-autogen, elixir-format +msgid "Examples" +msgstr "Beispiele" + +#: lib/mv_web/live/contribution_settings_live.ex:262 +#: lib/mv_web/live/contribution_type_live/index.ex:172 +#, elixir-autogen, elixir-format +msgid "Family" +msgstr "Familie" + +#: lib/mv_web/live/contribution_type_live/index.ex:128 +#, elixir-autogen, elixir-format +msgid "Fixed after creation. Members can only switch between types with the same interval." +msgstr "Nach der Erstellung unveränderlich. Mitglieder können nur zwischen Arten mit demselben Intervall wechseln." + +#: lib/mv_web/live/contribution_settings_live.ex:228 +#, elixir-autogen, elixir-format +msgid "Generated periods" +msgstr "Generierte Beiträge" + +#: lib/mv_web/live/contribution_settings_live.ex:52 +#, elixir-autogen, elixir-format +msgid "Global Settings" +msgstr "Globale Einstellungen" + +#: lib/mv_web/live/contribution_period_live/show.ex:344 +#: lib/mv_web/live/contribution_settings_live.ex:275 +#: lib/mv_web/live/contribution_type_live/index.ex:203 +#, elixir-autogen, elixir-format +msgid "Half-yearly" +msgstr "Halbjährlich" + +#: lib/mv_web/live/contribution_type_live/index.ex:181 +#, elixir-autogen, elixir-format +msgid "Half-yearly contribution for supporting members" +msgstr "Halbjährlicher Beitrag für Fördermitglieder" + +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_type_live/index.ex:188 +#, elixir-autogen, elixir-format +msgid "Honorary" +msgstr "Ehrenmitglied" + +#: lib/mv_web/live/contribution_settings_live.ex:85 +#, elixir-autogen, elixir-format +msgid "Include joining period" +msgstr "Zahlt ab Zeitpunkt des Eintritts" + +#: lib/mv_web/live/contribution_period_live/show.ex:137 +#: lib/mv_web/live/contribution_type_live/index.ex:57 +#: lib/mv_web/live/contribution_type_live/index.ex:127 +#, elixir-autogen, elixir-format +msgid "Interval" +msgstr "Intervall" + +#: lib/mv_web/live/contribution_settings_live.ex:220 +#, elixir-autogen, elixir-format +msgid "Joining date" +msgstr "Eintrittsdatum" + +#: lib/mv_web/live/contribution_period_live/show.ex:332 +#, elixir-autogen, elixir-format +msgid "Joining year - reduced to 0" +msgstr "Eintrittsjahr - auf 0 reduziert" + +#: lib/mv_web/live/contribution_type_live/index.ex:38 +#, elixir-autogen, elixir-format +msgid "Manage contribution types for membership fees." +msgstr "Beitragsarten für Mitgliedsbeiträge verwalten." + +#: lib/mv_web/live/contribution_period_live/show.ex:116 +#, elixir-autogen, elixir-format +msgid "Mark as Paid" +msgstr "Als bezahlt markieren" + +#: lib/mv_web/live/contribution_period_live/show.ex:120 +#, elixir-autogen, elixir-format +msgid "Mark as Suspended" +msgstr "Als ausgesetzt markieren" + +#: lib/mv_web/live/contribution_period_live/show.ex:124 +#, elixir-autogen, elixir-format +msgid "Mark as Unpaid" +msgstr "Als unbezahlt markieren" + +#: lib/mv_web/live/contribution_period_live/show.ex:26 +#, elixir-autogen, elixir-format +msgid "Member Contributions" +msgstr "Mitgliedsbeiträge" + +#: lib/mv_web/live/contribution_settings_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Member pays for the year they joined" +msgstr "Mitglied zahlt für das Eintrittsjahr" + +#: lib/mv_web/live/contribution_settings_live.ex:155 +#, elixir-autogen, elixir-format +msgid "Member pays from the joining month" +msgstr "Mitglied zahlt ab dem Eintrittsmonat" + +#: lib/mv_web/live/contribution_settings_live.ex:144 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full quarter" +msgstr "Mitglied zahlt ab dem nächsten vollen Quartal" + +#: lib/mv_web/live/contribution_settings_live.ex:133 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full year" +msgstr "Mitglied zahlt ab dem nächsten vollen Jahr" + +#: lib/mv_web/live/contribution_period_live/show.ex:43 +#, elixir-autogen, elixir-format +msgid "Member since" +msgstr "Mitglied seit" + +#: lib/mv_web/live/contribution_period_live/show.ex:92 +#, elixir-autogen, elixir-format +msgid "Members can only switch between contribution types with the same payment interval (e.g., yearly to yearly). This prevents complex period overlaps." +msgstr "Mitglieder können nur zwischen Beitragsarten mit demselben Zahlungsintervall wechseln (z.B. jährlich zu jährlich). Dies verhindert komplexe Periodenüberschneidungen." + +#: lib/mv_web/live/contribution_period_live/show.ex:342 +#: lib/mv_web/live/contribution_settings_live.ex:273 +#: lib/mv_web/live/contribution_type_live/index.ex:201 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "Monatlich" + +#: lib/mv_web/live/contribution_settings_live.ex:150 +#, elixir-autogen, elixir-format +msgid "Monthly Interval - Joining Period Included" +msgstr "Monatliches Intervall - Eintrittsperiode eingeschlossen" + +#: lib/mv_web/live/contribution_type_live/index.ex:165 +#, elixir-autogen, elixir-format +msgid "Monthly fee for students and trainees" +msgstr "Monatlicher Beitrag für Studierende und Auszubildende" + +#: lib/mv_web/live/contribution_type_live/index.ex:123 +#, elixir-autogen, elixir-format +msgid "Name & Amount" +msgstr "Name & Betrag" + +#: lib/mv_web/live/contribution_type_live/index.ex:42 +#, elixir-autogen, elixir-format +msgid "New Contribution Type" +msgstr "Neue Beitragsart" + +#: lib/mv_web/live/contribution_type_live/index.ex:189 +#, elixir-autogen, elixir-format +msgid "No fee for honorary members" +msgstr "Kein Beitrag für Ehrenmitglieder" + +#: lib/mv_web/live/contribution_type_live/index.ex:134 +#, elixir-autogen, elixir-format +msgid "Only possible if no members are assigned to this type." +msgstr "Nur möglich, wenn keine Mitglieder dieser Art zugewiesen sind." + +#: lib/mv_web/live/contribution_period_live/show.ex:70 +#, elixir-autogen, elixir-format +msgid "Open Contributions" +msgstr "Offene Beiträge" + +#: lib/mv_web/live/contribution_period_live/show.ex:302 +#, elixir-autogen, elixir-format +msgid "Paid via bank transfer" +msgstr "Per Überweisung bezahlt" + +#: lib/mv_web/live/contribution_period_live/show.ex:226 +#: lib/mv_web/live/contribution_settings_live.ex:197 +#: lib/mv_web/live/contribution_type_live/index.ex:97 +#, elixir-autogen, elixir-format +msgid "Preview Mockup" +msgstr "Vorschau" + +#: lib/mv_web/live/contribution_period_live/show.ex:343 +#: lib/mv_web/live/contribution_settings_live.ex:274 +#: lib/mv_web/live/contribution_type_live/index.ex:202 +#, elixir-autogen, elixir-format +msgid "Quarterly" +msgstr "Quartalsweise" + +#: lib/mv_web/live/contribution_settings_live.ex:139 +#, elixir-autogen, elixir-format +msgid "Quarterly Interval - Joining Period Excluded" +msgstr "Quartalsintervall - Eintrittsperiode ausgeschlossen" + +#: lib/mv_web/live/contribution_type_live/index.ex:173 +#, elixir-autogen, elixir-format +msgid "Quarterly fee for family memberships" +msgstr "Quartalsbeitrag für Familienmitgliedschaften" + +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_settings_live.ex:250 +#: lib/mv_web/live/contribution_type_live/index.ex:156 +#, elixir-autogen, elixir-format +msgid "Reduced" +msgstr "Ermäßigt" + +#: lib/mv_web/live/contribution_type_live/index.ex:157 +#, elixir-autogen, elixir-format +msgid "Reduced fee for unemployed, pensioners, or low income" +msgstr "Ermäßigter Beitrag für Arbeitslose, Rentner*innen oder Geringverdienende" + +#: lib/mv_web/live/contribution_period_live/show.ex:276 +#: lib/mv_web/live/contribution_settings_live.ex:244 +#: lib/mv_web/live/contribution_type_live/index.ex:148 +#, elixir-autogen, elixir-format +msgid "Regular" +msgstr "Regulär" + +#: lib/mv_web/live/contribution_period_live/show.ex:204 +#, elixir-autogen, elixir-format +msgid "Reopen" +msgstr "Wieder öffnen" + +#: lib/mv_web/live/contribution_settings_live.ex:176 +#, elixir-autogen, elixir-format +msgid "See how the contribution periods will be displayed for an individual member. This example shows Maria Weber with multiple contribution periods." +msgstr "Sehen Sie, wie die Beitragsperioden für ein einzelnes Mitglied angezeigt werden. Dieses Beispiel zeigt Maria Weber mit mehreren Beitragsperioden." + +#: lib/mv_web/live/contribution_type_live/index.ex:149 +#, elixir-autogen, elixir-format +msgid "Standard membership fee for regular members" +msgstr "Standard-Mitgliedsbeitrag für reguläre Mitglieder" + +#: lib/mv_web/live/contribution_period_live/show.ex:139 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Status" + +#: lib/mv_web/live/contribution_settings_live.ex:256 +#: lib/mv_web/live/contribution_type_live/index.ex:164 +#, elixir-autogen, elixir-format +msgid "Student" +msgstr "Student*in" + +#: lib/mv_web/live/contribution_type_live/index.ex:180 +#, elixir-autogen, elixir-format +msgid "Supporting Member" +msgstr "Fördermitglied" + +#: lib/mv_web/live/contribution_period_live/show.ex:195 +#, elixir-autogen, elixir-format +msgid "Suspend" +msgstr "Aussetzen" + +#: lib/mv_web/live/contribution_period_live/show.ex:260 +#, elixir-autogen, elixir-format +msgid "Suspended" +msgstr "Ausgesetzt" + +#: lib/mv_web/live/contribution_settings_live.ex:69 +#, elixir-autogen, elixir-format +msgid "This contribution type is automatically assigned to all new members. Can be changed individually per member." +msgstr "Diese Beitragsart wird automatisch allen neuen Mitgliedern zugewiesen. Kann pro Mitglied individuell geändert werden." + +#: lib/mv_web/live/contribution_period_live/show.ex:228 +#: lib/mv_web/live/contribution_settings_live.ex:199 +#: lib/mv_web/live/contribution_type_live/index.ex:99 +#, elixir-autogen, elixir-format +msgid "This page is not functional and only displays the planned features." +msgstr "Diese Seite ist nicht funktional und zeigt nur die geplanten Funktionen." + +#: lib/mv_web/live/contribution_period_live/show.ex:136 +#, elixir-autogen, elixir-format +msgid "Time Period" +msgstr "Zeitraum" + +#: lib/mv_web/live/contribution_period_live/show.ex:66 +#, elixir-autogen, elixir-format +msgid "Total Contributions" +msgstr "Beiträge gesamt" + +#: lib/mv_web/live/contribution_period_live/show.ex:251 +#, elixir-autogen, elixir-format +msgid "Unpaid" +msgstr "Unbezahlt" + +#: lib/mv_web/live/contribution_settings_live.ex:183 +#, elixir-autogen, elixir-format +msgid "View Example Member" +msgstr "Beispielmitglied ansehen" + +#: lib/mv_web/live/contribution_settings_live.ex:90 +#, elixir-autogen, elixir-format +msgid "When active: Members pay from the period of their joining." +msgstr "Wenn aktiv: Mitglieder zahlen ab der Periode ihres Eintritts." + +#: lib/mv_web/live/contribution_settings_live.ex:93 +#, elixir-autogen, elixir-format +msgid "When inactive: Members pay from the next full period after joining." +msgstr "Wenn inaktiv: Mitglieder zahlen ab der nächsten vollen Periode nach dem Eintritt." + +#: lib/mv_web/live/contribution_period_live/show.ex:98 +#, elixir-autogen, elixir-format +msgid "Why are not all contribution types shown?" +msgstr "Warum werden nicht alle Beitragsarten angezeigt?" + +#: lib/mv_web/live/contribution_period_live/show.ex:85 +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_period_live/show.ex:345 +#: lib/mv_web/live/contribution_settings_live.ex:276 +#: lib/mv_web/live/contribution_type_live/index.ex:204 +#, elixir-autogen, elixir-format +msgid "Yearly" +msgstr "Jährlich" + +#: lib/mv_web/live/contribution_settings_live.ex:128 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Excluded" +msgstr "Jährliches Intervall - Eintrittsperiode ausgeschlossen" + +#: lib/mv_web/live/contribution_settings_live.ex:117 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Included" +msgstr "Jährliches Intervall - Eintrittsperiode eingeschlossen" + #~ #: lib/mv_web/live/member_live/form.ex:48 #~ #: lib/mv_web/live/member_live/show.ex:51 #~ #, elixir-autogen, elixir-format diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 1e0e954..8594746 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -12,6 +12,7 @@ msgid "" msgstr "" #: lib/mv_web/components/core_components.ex:386 +#: lib/mv_web/live/contribution_period_live/show.ex:141 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" @@ -35,12 +36,14 @@ msgstr "" msgid "City" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:78 #: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:66 #: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 @@ -54,6 +57,7 @@ msgstr "" msgid "Edit Member" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:58 #: lib/mv_web/live/member_live/form.ex:47 #: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 @@ -122,6 +126,7 @@ msgstr "" msgid "House Number" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:140 #: lib/mv_web/live/member_live/form.ex:52 #: lib/mv_web/live/member_live/show.ex:57 #, elixir-autogen, elixir-format @@ -130,6 +135,8 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 +#: lib/mv_web/live/contribution_period_live/show.ex:186 +#: lib/mv_web/live/contribution_period_live/show.ex:242 #: lib/mv_web/live/member_live/form.ex:48 #: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 @@ -289,7 +296,7 @@ msgstr "" msgid "Immutable" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:102 +#: lib/mv_web/components/layouts/navbar.ex:113 #, elixir-autogen, elixir-format msgid "Logout" msgstr "" @@ -306,12 +313,14 @@ msgid "Member" msgstr "" #: lib/mv_web/components/layouts/navbar.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:61 #: lib/mv_web/live/member_live/index.ex:73 #: lib/mv_web/live/member_live/index.html.heex:3 #, elixir-autogen, elixir-format msgid "Members" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:48 #: lib/mv_web/live/custom_field_live/form.ex:51 #, elixir-autogen, elixir-format msgid "Name" @@ -332,6 +341,7 @@ msgstr "" msgid "Not set" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:207 #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 #: lib/mv_web/live/user_live/form.ex:224 @@ -350,7 +360,7 @@ msgstr "" msgid "Password Authentication" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:95 +#: lib/mv_web/components/layouts/navbar.ex:106 #, elixir-autogen, elixir-format msgid "Profil" msgstr "" @@ -370,7 +380,7 @@ msgstr "" msgid "Select member" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:99 +#: lib/mv_web/components/layouts/navbar.ex:110 #, elixir-autogen, elixir-format msgid "Settings" msgstr "" @@ -538,14 +548,14 @@ msgstr "" msgid "Back to users list" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:33 -#: lib/mv_web/components/layouts/navbar.ex:39 +#: lib/mv_web/components/layouts/navbar.ex:44 +#: lib/mv_web/components/layouts/navbar.ex:50 #, elixir-autogen, elixir-format msgid "Select language" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:46 -#: lib/mv_web/components/layouts/navbar.ex:66 +#: lib/mv_web/components/layouts/navbar.ex:57 +#: lib/mv_web/components/layouts/navbar.ex:77 #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr "" @@ -717,6 +727,7 @@ msgstr "" msgid "Manage global settings for the association." msgstr "" +#: lib/mv_web/live/contribution_settings_live.ex:102 #: lib/mv_web/live/global_settings_live.ex:56 #, elixir-autogen, elixir-format msgid "Save Settings" @@ -853,3 +864,429 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Payment filter" msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:107 +#, elixir-autogen, elixir-format +msgid "%{count} period selected" +msgid_plural "%{count} periods selected" +msgstr[0] "" +msgstr[1] "" + +#: lib/mv_web/live/contribution_type_live/index.ex:113 +#, elixir-autogen, elixir-format +msgid "About Contribution Types" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:138 +#: lib/mv_web/live/contribution_type_live/index.ex:53 +#, elixir-autogen, elixir-format +msgid "Amount" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:48 +#, elixir-autogen, elixir-format +msgid "Back to Settings" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:124 +#, elixir-autogen, elixir-format +msgid "Can be changed at any time. Amount changes affect future periods only." +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:77 +#, elixir-autogen, elixir-format +msgid "Cannot delete - members assigned" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:83 +#, elixir-autogen, elixir-format +msgid "Change Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:42 +#, elixir-autogen, elixir-format +msgid "Configure global settings for membership contributions." +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:34 +#: lib/mv_web/live/contribution_settings_live.ex:27 +#: lib/mv_web/live/contribution_settings_live.ex:40 +#, elixir-autogen, elixir-format +msgid "Contribution Settings" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:62 +#, elixir-autogen, elixir-format +msgid "Contribution Start" +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:32 +#: lib/mv_web/live/contribution_type_live/index.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:36 +#, elixir-autogen, elixir-format +msgid "Contribution Types" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:224 +#, elixir-autogen, elixir-format +msgid "Contribution start" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:41 +#, elixir-autogen, elixir-format +msgid "Contribution type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:117 +#, elixir-autogen, elixir-format +msgid "Contribution types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation." +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:30 +#, elixir-autogen, elixir-format +msgid "Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:39 +#, elixir-autogen, elixir-format +msgid "Contributions for %{name}" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:159 +#, elixir-autogen, elixir-format +msgid "Current" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:60 +#, elixir-autogen, elixir-format +msgid "Default Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:133 +#, elixir-autogen, elixir-format +msgid "Deletion" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:173 +#, elixir-autogen, elixir-format +msgid "Example: Member Contribution View" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:113 +#, elixir-autogen, elixir-format +msgid "Examples" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:262 +#: lib/mv_web/live/contribution_type_live/index.ex:172 +#, elixir-autogen, elixir-format +msgid "Family" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:128 +#, elixir-autogen, elixir-format +msgid "Fixed after creation. Members can only switch between types with the same interval." +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:228 +#, elixir-autogen, elixir-format +msgid "Generated periods" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:52 +#, elixir-autogen, elixir-format +msgid "Global Settings" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:344 +#: lib/mv_web/live/contribution_settings_live.ex:275 +#: lib/mv_web/live/contribution_type_live/index.ex:203 +#, elixir-autogen, elixir-format +msgid "Half-yearly" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:181 +#, elixir-autogen, elixir-format +msgid "Half-yearly contribution for supporting members" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_type_live/index.ex:188 +#, elixir-autogen, elixir-format +msgid "Honorary" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:85 +#, elixir-autogen, elixir-format +msgid "Include joining period" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:137 +#: lib/mv_web/live/contribution_type_live/index.ex:57 +#: lib/mv_web/live/contribution_type_live/index.ex:127 +#, elixir-autogen, elixir-format +msgid "Interval" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:220 +#, elixir-autogen, elixir-format +msgid "Joining date" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:332 +#, elixir-autogen, elixir-format +msgid "Joining year - reduced to 0" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:38 +#, elixir-autogen, elixir-format +msgid "Manage contribution types for membership fees." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:116 +#, elixir-autogen, elixir-format +msgid "Mark as Paid" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:120 +#, elixir-autogen, elixir-format +msgid "Mark as Suspended" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:124 +#, elixir-autogen, elixir-format +msgid "Mark as Unpaid" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:26 +#, elixir-autogen, elixir-format +msgid "Member Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Member pays for the year they joined" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:155 +#, elixir-autogen, elixir-format +msgid "Member pays from the joining month" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:144 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full quarter" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:133 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full year" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:43 +#, elixir-autogen, elixir-format +msgid "Member since" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:92 +#, elixir-autogen, elixir-format +msgid "Members can only switch between contribution types with the same payment interval (e.g., yearly to yearly). This prevents complex period overlaps." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:342 +#: lib/mv_web/live/contribution_settings_live.ex:273 +#: lib/mv_web/live/contribution_type_live/index.ex:201 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:150 +#, elixir-autogen, elixir-format +msgid "Monthly Interval - Joining Period Included" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:165 +#, elixir-autogen, elixir-format +msgid "Monthly fee for students and trainees" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:123 +#, elixir-autogen, elixir-format +msgid "Name & Amount" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:42 +#, elixir-autogen, elixir-format +msgid "New Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:189 +#, elixir-autogen, elixir-format +msgid "No fee for honorary members" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:134 +#, elixir-autogen, elixir-format +msgid "Only possible if no members are assigned to this type." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:70 +#, elixir-autogen, elixir-format +msgid "Open Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:302 +#, elixir-autogen, elixir-format +msgid "Paid via bank transfer" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:226 +#: lib/mv_web/live/contribution_settings_live.ex:197 +#: lib/mv_web/live/contribution_type_live/index.ex:97 +#, elixir-autogen, elixir-format +msgid "Preview Mockup" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:343 +#: lib/mv_web/live/contribution_settings_live.ex:274 +#: lib/mv_web/live/contribution_type_live/index.ex:202 +#, elixir-autogen, elixir-format +msgid "Quarterly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:139 +#, elixir-autogen, elixir-format +msgid "Quarterly Interval - Joining Period Excluded" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:173 +#, elixir-autogen, elixir-format +msgid "Quarterly fee for family memberships" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_settings_live.ex:250 +#: lib/mv_web/live/contribution_type_live/index.ex:156 +#, elixir-autogen, elixir-format +msgid "Reduced" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:157 +#, elixir-autogen, elixir-format +msgid "Reduced fee for unemployed, pensioners, or low income" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:276 +#: lib/mv_web/live/contribution_settings_live.ex:244 +#: lib/mv_web/live/contribution_type_live/index.ex:148 +#, elixir-autogen, elixir-format +msgid "Regular" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:204 +#, elixir-autogen, elixir-format +msgid "Reopen" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:176 +#, elixir-autogen, elixir-format +msgid "See how the contribution periods will be displayed for an individual member. This example shows Maria Weber with multiple contribution periods." +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:149 +#, elixir-autogen, elixir-format +msgid "Standard membership fee for regular members" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:139 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:256 +#: lib/mv_web/live/contribution_type_live/index.ex:164 +#, elixir-autogen, elixir-format +msgid "Student" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:180 +#, elixir-autogen, elixir-format +msgid "Supporting Member" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:195 +#, elixir-autogen, elixir-format +msgid "Suspend" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:260 +#, elixir-autogen, elixir-format +msgid "Suspended" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:69 +#, elixir-autogen, elixir-format +msgid "This contribution type is automatically assigned to all new members. Can be changed individually per member." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:228 +#: lib/mv_web/live/contribution_settings_live.ex:199 +#: lib/mv_web/live/contribution_type_live/index.ex:99 +#, elixir-autogen, elixir-format +msgid "This page is not functional and only displays the planned features." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:136 +#, elixir-autogen, elixir-format +msgid "Time Period" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:66 +#, elixir-autogen, elixir-format +msgid "Total Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:251 +#, elixir-autogen, elixir-format +msgid "Unpaid" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:183 +#, elixir-autogen, elixir-format +msgid "View Example Member" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:90 +#, elixir-autogen, elixir-format +msgid "When active: Members pay from the period of their joining." +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:93 +#, elixir-autogen, elixir-format +msgid "When inactive: Members pay from the next full period after joining." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:98 +#, elixir-autogen, elixir-format +msgid "Why are not all contribution types shown?" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:85 +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_period_live/show.ex:345 +#: lib/mv_web/live/contribution_settings_live.ex:276 +#: lib/mv_web/live/contribution_type_live/index.ex:204 +#, elixir-autogen, elixir-format +msgid "Yearly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:128 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Excluded" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:117 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Included" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 319bcc3..cf081bf 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -12,6 +12,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/mv_web/components/core_components.ex:386 +#: lib/mv_web/live/contribution_period_live/show.ex:141 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" @@ -35,12 +36,14 @@ msgstr "" msgid "City" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:78 #: lib/mv_web/live/member_live/index.html.heex:250 #: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:66 #: lib/mv_web/live/member_live/index.html.heex:242 #: lib/mv_web/live/user_live/form.ex:265 #: lib/mv_web/live/user_live/index.html.heex:66 @@ -54,6 +57,7 @@ msgstr "" msgid "Edit Member" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:58 #: lib/mv_web/live/member_live/form.ex:47 #: lib/mv_web/live/member_live/index.html.heex:112 #: lib/mv_web/live/member_live/show.ex:50 @@ -122,6 +126,7 @@ msgstr "" msgid "House Number" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:140 #: lib/mv_web/live/member_live/form.ex:52 #: lib/mv_web/live/member_live/show.ex:57 #, elixir-autogen, elixir-format @@ -130,6 +135,8 @@ msgstr "" #: lib/mv_web/live/components/payment_filter_component.ex:94 #: lib/mv_web/live/components/payment_filter_component.ex:144 +#: lib/mv_web/live/contribution_period_live/show.ex:186 +#: lib/mv_web/live/contribution_period_live/show.ex:242 #: lib/mv_web/live/member_live/form.ex:48 #: lib/mv_web/live/member_live/index.html.heex:229 #: lib/mv_web/live/member_live/show.ex:51 @@ -289,7 +296,7 @@ msgstr "" msgid "Immutable" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:102 +#: lib/mv_web/components/layouts/navbar.ex:113 #, elixir-autogen, elixir-format msgid "Logout" msgstr "" @@ -306,12 +313,14 @@ msgid "Member" msgstr "" #: lib/mv_web/components/layouts/navbar.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:61 #: lib/mv_web/live/member_live/index.ex:73 #: lib/mv_web/live/member_live/index.html.heex:3 #, elixir-autogen, elixir-format msgid "Members" msgstr "" +#: lib/mv_web/live/contribution_type_live/index.ex:48 #: lib/mv_web/live/custom_field_live/form.ex:51 #, elixir-autogen, elixir-format msgid "Name" @@ -332,6 +341,7 @@ msgstr "" msgid "Not set" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:207 #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 #: lib/mv_web/live/user_live/form.ex:224 @@ -350,7 +360,7 @@ msgstr "" msgid "Password Authentication" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:95 +#: lib/mv_web/components/layouts/navbar.ex:106 #, elixir-autogen, elixir-format msgid "Profil" msgstr "" @@ -370,7 +380,7 @@ msgstr "" msgid "Select member" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:99 +#: lib/mv_web/components/layouts/navbar.ex:110 #, elixir-autogen, elixir-format msgid "Settings" msgstr "" @@ -538,14 +548,14 @@ msgstr "" msgid "Back to users list" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:33 -#: lib/mv_web/components/layouts/navbar.ex:39 +#: lib/mv_web/components/layouts/navbar.ex:44 +#: lib/mv_web/components/layouts/navbar.ex:50 #, elixir-autogen, elixir-format, fuzzy msgid "Select language" msgstr "" -#: lib/mv_web/components/layouts/navbar.ex:46 -#: lib/mv_web/components/layouts/navbar.ex:66 +#: lib/mv_web/components/layouts/navbar.ex:57 +#: lib/mv_web/components/layouts/navbar.ex:77 #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr "" @@ -717,6 +727,7 @@ msgstr "" msgid "Manage global settings for the association." msgstr "" +#: lib/mv_web/live/contribution_settings_live.ex:102 #: lib/mv_web/live/global_settings_live.ex:56 #, elixir-autogen, elixir-format, fuzzy msgid "Save Settings" @@ -854,8 +865,440 @@ msgstr "" msgid "Payment filter" msgstr "" +#: lib/mv_web/live/contribution_period_live/show.ex:107 +#, elixir-autogen, elixir-format +msgid "%{count} period selected" +msgid_plural "%{count} periods selected" +msgstr[0] "" +msgstr[1] "" + +#: lib/mv_web/live/contribution_type_live/index.ex:113 +#, elixir-autogen, elixir-format +msgid "About Contribution Types" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:138 +#: lib/mv_web/live/contribution_type_live/index.ex:53 +#, elixir-autogen, elixir-format +msgid "Amount" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:48 +#, elixir-autogen, elixir-format +msgid "Back to Settings" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:124 +#, elixir-autogen, elixir-format +msgid "Can be changed at any time. Amount changes affect future periods only." +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:77 +#, elixir-autogen, elixir-format +msgid "Cannot delete - members assigned" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:83 +#, elixir-autogen, elixir-format +msgid "Change Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:42 +#, elixir-autogen, elixir-format +msgid "Configure global settings for membership contributions." +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:34 +#: lib/mv_web/live/contribution_settings_live.ex:27 +#: lib/mv_web/live/contribution_settings_live.ex:40 +#, elixir-autogen, elixir-format +msgid "Contribution Settings" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:62 +#, elixir-autogen, elixir-format +msgid "Contribution Start" +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:32 +#: lib/mv_web/live/contribution_type_live/index.ex:25 +#: lib/mv_web/live/contribution_type_live/index.ex:36 +#, elixir-autogen, elixir-format +msgid "Contribution Types" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:224 +#, elixir-autogen, elixir-format +msgid "Contribution start" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:41 +#, elixir-autogen, elixir-format +msgid "Contribution type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:117 +#, elixir-autogen, elixir-format +msgid "Contribution types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation." +msgstr "" + +#: lib/mv_web/components/layouts/navbar.ex:30 +#, elixir-autogen, elixir-format +msgid "Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:39 +#, elixir-autogen, elixir-format +msgid "Contributions for %{name}" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:159 +#, elixir-autogen, elixir-format +msgid "Current" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:60 +#, elixir-autogen, elixir-format +msgid "Default Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:133 +#, elixir-autogen, elixir-format, fuzzy +msgid "Deletion" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:173 +#, elixir-autogen, elixir-format +msgid "Example: Member Contribution View" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:113 +#, elixir-autogen, elixir-format +msgid "Examples" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:262 +#: lib/mv_web/live/contribution_type_live/index.ex:172 +#, elixir-autogen, elixir-format +msgid "Family" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:128 +#, elixir-autogen, elixir-format +msgid "Fixed after creation. Members can only switch between types with the same interval." +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:228 +#, elixir-autogen, elixir-format +msgid "Generated periods" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:52 +#, elixir-autogen, elixir-format, fuzzy +msgid "Global Settings" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:344 +#: lib/mv_web/live/contribution_settings_live.ex:275 +#: lib/mv_web/live/contribution_type_live/index.ex:203 +#, elixir-autogen, elixir-format +msgid "Half-yearly" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:181 +#, elixir-autogen, elixir-format +msgid "Half-yearly contribution for supporting members" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_type_live/index.ex:188 +#, elixir-autogen, elixir-format +msgid "Honorary" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:85 +#, elixir-autogen, elixir-format +msgid "Include joining period" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:137 +#: lib/mv_web/live/contribution_type_live/index.ex:57 +#: lib/mv_web/live/contribution_type_live/index.ex:127 +#, elixir-autogen, elixir-format +msgid "Interval" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:220 +#, elixir-autogen, elixir-format, fuzzy +msgid "Joining date" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:332 +#, elixir-autogen, elixir-format +msgid "Joining year - reduced to 0" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:38 +#, elixir-autogen, elixir-format +msgid "Manage contribution types for membership fees." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:116 +#, elixir-autogen, elixir-format +msgid "Mark as Paid" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:120 +#, elixir-autogen, elixir-format +msgid "Mark as Suspended" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:124 +#, elixir-autogen, elixir-format +msgid "Mark as Unpaid" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:26 +#, elixir-autogen, elixir-format +msgid "Member Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Member pays for the year they joined" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:155 +#, elixir-autogen, elixir-format +msgid "Member pays from the joining month" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:144 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full quarter" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:133 +#, elixir-autogen, elixir-format +msgid "Member pays from the next full year" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:43 +#, elixir-autogen, elixir-format, fuzzy +msgid "Member since" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:92 +#, elixir-autogen, elixir-format +msgid "Members can only switch between contribution types with the same payment interval (e.g., yearly to yearly). This prevents complex period overlaps." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:342 +#: lib/mv_web/live/contribution_settings_live.ex:273 +#: lib/mv_web/live/contribution_type_live/index.ex:201 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:150 +#, elixir-autogen, elixir-format +msgid "Monthly Interval - Joining Period Included" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:165 +#, elixir-autogen, elixir-format +msgid "Monthly fee for students and trainees" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:123 +#, elixir-autogen, elixir-format +msgid "Name & Amount" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:42 +#, elixir-autogen, elixir-format +msgid "New Contribution Type" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:189 +#, elixir-autogen, elixir-format +msgid "No fee for honorary members" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:134 +#, elixir-autogen, elixir-format +msgid "Only possible if no members are assigned to this type." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:70 +#, elixir-autogen, elixir-format +msgid "Open Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:302 +#, elixir-autogen, elixir-format +msgid "Paid via bank transfer" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:226 +#: lib/mv_web/live/contribution_settings_live.ex:197 +#: lib/mv_web/live/contribution_type_live/index.ex:97 +#, elixir-autogen, elixir-format +msgid "Preview Mockup" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:343 +#: lib/mv_web/live/contribution_settings_live.ex:274 +#: lib/mv_web/live/contribution_type_live/index.ex:202 +#, elixir-autogen, elixir-format +msgid "Quarterly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:139 +#, elixir-autogen, elixir-format +msgid "Quarterly Interval - Joining Period Excluded" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:173 +#, elixir-autogen, elixir-format +msgid "Quarterly fee for family memberships" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_settings_live.ex:250 +#: lib/mv_web/live/contribution_type_live/index.ex:156 +#, elixir-autogen, elixir-format +msgid "Reduced" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:157 +#, elixir-autogen, elixir-format +msgid "Reduced fee for unemployed, pensioners, or low income" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:276 +#: lib/mv_web/live/contribution_settings_live.ex:244 +#: lib/mv_web/live/contribution_type_live/index.ex:148 +#, elixir-autogen, elixir-format +msgid "Regular" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:204 +#, elixir-autogen, elixir-format +msgid "Reopen" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:176 +#, elixir-autogen, elixir-format +msgid "See how the contribution periods will be displayed for an individual member. This example shows Maria Weber with multiple contribution periods." +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:149 +#, elixir-autogen, elixir-format +msgid "Standard membership fee for regular members" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:139 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:256 +#: lib/mv_web/live/contribution_type_live/index.ex:164 +#, elixir-autogen, elixir-format +msgid "Student" +msgstr "" + +#: lib/mv_web/live/contribution_type_live/index.ex:180 +#, elixir-autogen, elixir-format +msgid "Supporting Member" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:195 +#, elixir-autogen, elixir-format +msgid "Suspend" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:260 +#, elixir-autogen, elixir-format +msgid "Suspended" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:69 +#, elixir-autogen, elixir-format +msgid "This contribution type is automatically assigned to all new members. Can be changed individually per member." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:228 +#: lib/mv_web/live/contribution_settings_live.ex:199 +#: lib/mv_web/live/contribution_type_live/index.ex:99 +#, elixir-autogen, elixir-format +msgid "This page is not functional and only displays the planned features." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:136 +#, elixir-autogen, elixir-format +msgid "Time Period" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:66 +#, elixir-autogen, elixir-format +msgid "Total Contributions" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:251 +#, elixir-autogen, elixir-format +msgid "Unpaid" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:183 +#, elixir-autogen, elixir-format +msgid "View Example Member" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:90 +#, elixir-autogen, elixir-format +msgid "When active: Members pay from the period of their joining." +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:93 +#, elixir-autogen, elixir-format +msgid "When inactive: Members pay from the next full period after joining." +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:98 +#, elixir-autogen, elixir-format +msgid "Why are not all contribution types shown?" +msgstr "" + +#: lib/mv_web/live/contribution_period_live/show.ex:85 +#: lib/mv_web/live/contribution_period_live/show.ex:86 +#: lib/mv_web/live/contribution_period_live/show.ex:87 +#: lib/mv_web/live/contribution_period_live/show.ex:345 +#: lib/mv_web/live/contribution_settings_live.ex:276 +#: lib/mv_web/live/contribution_type_live/index.ex:204 +#, elixir-autogen, elixir-format +msgid "Yearly" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:128 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Excluded" +msgstr "" + +#: lib/mv_web/live/contribution_settings_live.ex:117 +#, elixir-autogen, elixir-format +msgid "Yearly Interval - Joining Period Included" +msgstr "" + #~ #: lib/mv_web/live/member_live/form.ex:48 #~ #: lib/mv_web/live/member_live/show.ex:51 #~ #, elixir-autogen, elixir-format #~ msgid "Birth Date" #~ msgstr "" + +#~ #: lib/mv_web/live/contribution_period_live/show.ex:273 +#~ #: lib/mv_web/live/contribution_settings_live.ex:248 +#~ #, elixir-autogen, elixir-format +#~ msgid "Related Pages" +#~ msgstr "" From 4057b2d631884be83d2405f5b38d39b16383d56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Wed, 3 Dec 2025 14:06:24 +0100 Subject: [PATCH 15/16] Extend gettext conflict script to other conflict marker styles --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 876591d..25fb35c 100644 --- a/Justfile +++ b/Justfile @@ -90,7 +90,7 @@ clean: remove-gettext-conflicts: #!/usr/bin/env bash set -euo pipefail - find priv/gettext -type f -exec sed -i '/^<<<<<<< HEAD$/d; /^=======$/d; /^>>>>>>>/d' {} \; + find priv/gettext -type f -exec sed -i '/^<<<<<<>>>>>>/d; /^%%%%%%%/d; /^++++++/d; s/^+//' {} \; # Production environment commands # ================================ From e9bcb25d205cfefd18a43cea4259cfb36a839004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Wed, 3 Dec 2025 10:41:40 +0100 Subject: [PATCH 16/16] Fix postgres errors when running tests --- config/test.exs | 3 +++ lib/mv_web/endpoint.ex | 5 +++++ test/support/conn_case.ex | 10 ++++++++-- test/support/data_case.ex | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/config/test.exs b/config/test.exs index bcb55eb..2c4d2ba 100644 --- a/config/test.exs +++ b/config/test.exs @@ -45,3 +45,6 @@ config :mv, :token_signing_secret, "test_secret_key_for_ash_authentication_token config :mv, :session_identifier, :unsafe config :mv, :require_token_presence_for_authentication, false + +# Enable SQL Sandbox for async LiveView tests +config :mv, :sql_sandbox, true diff --git a/lib/mv_web/endpoint.ex b/lib/mv_web/endpoint.ex index 97dcae4..d1b4247 100644 --- a/lib/mv_web/endpoint.ex +++ b/lib/mv_web/endpoint.ex @@ -39,6 +39,11 @@ defmodule MvWeb.Endpoint do plug Phoenix.Ecto.CheckRepoStatus, otp_app: :mv end + # Enable Ecto SQL Sandbox in test environment for async tests + if Application.compile_env(:mv, :sql_sandbox) do + plug Phoenix.Ecto.SQL.Sandbox + end + plug Phoenix.LiveDashboard.RequestLogger, param_key: "request_logger", cookie_key: "request_logger" diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 0ee2364..853a326 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -123,7 +123,13 @@ defmodule MvWeb.ConnCase do end setup tags do - Mv.DataCase.setup_sandbox(tags) - {:ok, conn: Phoenix.ConnTest.build_conn()} + pid = Mv.DataCase.setup_sandbox(tags) + + conn = Phoenix.ConnTest.build_conn() + # Set metadata for Phoenix.Ecto.SQL.Sandbox plug to allow LiveView processes + # to share the test's database connection in async tests + conn = Plug.Conn.put_private(conn, :ecto_sandbox, pid) + + {:ok, conn: conn} end end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 6e53c38..4ba75ef 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -34,10 +34,12 @@ defmodule Mv.DataCase do @doc """ Sets up the sandbox based on the test tags. + Returns the owner pid for use with Phoenix.Ecto.SQL.Sandbox. """ def setup_sandbox(tags) do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Mv.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) + pid end @doc """