Compare commits
12 commits
cbadaa8b14
...
225bf8b681
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
225bf8b681 | ||
| e99dbdfb82 | |||
| b18f895939 | |||
| ce542eae3e | |||
| 2b1f49d60a | |||
| 49bd2eee0b | |||
| cecb547bd6 | |||
| 3f07de1276 | |||
| 911f308a67 | |||
| b1a9eb8b1d | |||
| 46f9094e1f | |||
| 2e4d14dd60 |
17 changed files with 538 additions and 257 deletions
|
|
@ -273,7 +273,7 @@ environment:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: renovate
|
- name: renovate
|
||||||
image: renovate/renovate:43.19
|
image: renovate/renovate:43.24
|
||||||
environment:
|
environment:
|
||||||
RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
|
RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
|
||||||
RENOVATE_TOKEN:
|
RENOVATE_TOKEN:
|
||||||
|
|
|
||||||
|
|
@ -356,9 +356,9 @@ lib/
|
||||||
- Screen readers must be able to navigate and understand the interface
|
- Screen readers must be able to navigate and understand the interface
|
||||||
- ARIA labels and roles must be properly set
|
- ARIA labels and roles must be properly set
|
||||||
|
|
||||||
**Group Badges in Member Overview:**
|
**Group Badges and Links in Member Overview / Detail:**
|
||||||
- Badges must have `role="status"` and appropriate `aria-label` attributes
|
- Use `aria-label` to indicate group membership (e.g. "Member of group X"). Do not use `role="status"` on badges or links—that role is for live regions (screen reader announcements), not for navigation or static labels.
|
||||||
- Badge title should indicate group membership
|
- Badge/link text or title should indicate group membership for screen readers.
|
||||||
|
|
||||||
**Clickable Group Badge (for filtering) - Optional:**
|
**Clickable Group Badge (for filtering) - Optional:**
|
||||||
|
|
||||||
|
|
@ -961,6 +961,8 @@ Each functional unit can be implemented as a **separate issue**:
|
||||||
### Issue 4: Member Detail - Groups Display
|
### Issue 4: Member Detail - Groups Display
|
||||||
**Type:** Frontend
|
**Type:** Frontend
|
||||||
**Estimation:** 1-2h
|
**Estimation:** 1-2h
|
||||||
|
**Status:** Implemented (Groups as data field in Personal Data, below Linked User; button-style links to `/groups/:slug`).
|
||||||
|
|
||||||
**Tasks:**
|
**Tasks:**
|
||||||
- Add groups section to member show
|
- Add groups section to member show
|
||||||
- Display group badges
|
- Display group badges
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule Mv.Membership.Import.ImportRunner do
|
defmodule Mv.Membership.Import.ImportRunner do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Orchestrates CSV member import: file reading, progress tracking, chunk processing,
|
Orchestrates CSV member import: file reading, progress tracking, chunk processing,
|
||||||
and error formatting. Used by `MvWeb.ImportExportLive` to keep LiveView thin.
|
and error formatting. Used by `MvWeb.ImportLive` to keep LiveView thin.
|
||||||
|
|
||||||
This module does not depend on Phoenix or LiveView. It provides pure functions for
|
This module does not depend on Phoenix or LiveView. It provides pure functions for
|
||||||
progress/merge and side-effectful helpers (read_file_entry, process_chunk) that
|
progress/merge and side-effectful helpers (read_file_entry, process_chunk) that
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ defmodule MvWeb.Layouts.Sidebar do
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if can_access_page?(@current_user, PagePaths.settings()) do %>
|
<%= if can_access_page?(@current_user, PagePaths.settings()) do %>
|
||||||
<.menu_subitem href={~p"/admin/import-export"} label={gettext("Import/Export")} />
|
<.menu_subitem href={~p"/admin/import"} label={gettext("Import")} />
|
||||||
<.menu_subitem href={~p"/settings"} label={gettext("Settings")} />
|
<.menu_subitem href={~p"/settings"} label={gettext("Settings")} />
|
||||||
<% end %>
|
<% end %>
|
||||||
</.menu_group>
|
</.menu_group>
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
send(self(), {:editing_section_changed, nil})
|
send(self(), {:editing_section_changed, nil})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get actor from assigns or fall back to socket assigns
|
||||||
|
actor = Map.get(assigns, :actor, socket.assigns[:actor])
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(assigns)
|
|> assign(assigns)
|
||||||
|
|
@ -228,7 +231,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
|> assign_new(:show_delete_modal, fn -> false end)
|
|> assign_new(:show_delete_modal, fn -> false end)
|
||||||
|> assign_new(:custom_field_to_delete, fn -> nil end)
|
|> assign_new(:custom_field_to_delete, fn -> nil end)
|
||||||
|> assign_new(:slug_confirmation, fn -> "" end)
|
|> assign_new(:slug_confirmation, fn -> "" end)
|
||||||
|> stream(:custom_fields, stream_custom_fields(assigns[:actor], self()), reset: true)}
|
|> stream(:custom_fields, stream_custom_fields(actor, self()), reset: true)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule MvWeb.ImportExportLive do
|
defmodule MvWeb.ImportLive do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
LiveView for importing and exporting members via CSV.
|
LiveView for importing members via CSV.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- CSV member import (admin only)
|
- CSV member import (admin only)
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule MvWeb.ImportExportLive do
|
||||||
alias Mv.Membership.Import.ImportRunner
|
alias Mv.Membership.Import.ImportRunner
|
||||||
alias Mv.Membership.Import.MemberCSV
|
alias Mv.Membership.Import.MemberCSV
|
||||||
alias MvWeb.Authorization
|
alias MvWeb.Authorization
|
||||||
alias MvWeb.ImportExportLive.Components
|
alias MvWeb.ImportLive.Components
|
||||||
|
|
||||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ defmodule MvWeb.ImportExportLive do
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, gettext("Import/Export"))
|
|> assign(:page_title, gettext("Import"))
|
||||||
|> assign(:club_name, club_name)
|
|> assign(:club_name, club_name)
|
||||||
|> assign(:import_state, nil)
|
|> assign(:import_state, nil)
|
||||||
|> assign(:import_progress, nil)
|
|> assign(:import_progress, nil)
|
||||||
|
|
@ -90,13 +90,6 @@ defmodule MvWeb.ImportExportLive do
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<Layouts.app flash={@flash} current_user={@current_user} club_name={@club_name}>
|
<Layouts.app flash={@flash} current_user={@current_user} club_name={@club_name}>
|
||||||
<.header>
|
|
||||||
{gettext("Import/Export")}
|
|
||||||
<:subtitle>
|
|
||||||
{gettext("Import members from CSV files or export member data.")}
|
|
||||||
</:subtitle>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<%= if Authorization.can?(@current_user, :create, Mv.Membership.Member) do %>
|
<%= if Authorization.can?(@current_user, :create, Mv.Membership.Member) do %>
|
||||||
<%!-- CSV Import Section --%>
|
<%!-- CSV Import Section --%>
|
||||||
<.form_section title={gettext("Import Members (CSV)")}>
|
<.form_section title={gettext("Import Members (CSV)")}>
|
||||||
|
|
@ -107,18 +100,6 @@ defmodule MvWeb.ImportExportLive do
|
||||||
<Components.import_progress {assigns} />
|
<Components.import_progress {assigns} />
|
||||||
<% end %>
|
<% end %>
|
||||||
</.form_section>
|
</.form_section>
|
||||||
|
|
||||||
<%!-- Export Section (Placeholder) --%>
|
|
||||||
<.form_section title={gettext("Export Members (CSV)")}>
|
|
||||||
<div role="note" class="alert alert-info">
|
|
||||||
<.icon name="hero-information-circle" class="size-5" aria-hidden="true" />
|
|
||||||
<div>
|
|
||||||
<p class="text-sm">
|
|
||||||
{gettext("Export functionality will be available in a future release.")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</.form_section>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div role="alert" class="alert alert-error">
|
<div role="alert" class="alert alert-error">
|
||||||
<.icon name="hero-exclamation-circle" class="size-5" aria-hidden="true" />
|
<.icon name="hero-exclamation-circle" class="size-5" aria-hidden="true" />
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule MvWeb.ImportExportLive.Components do
|
defmodule MvWeb.ImportLive.Components do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Function components for the Import/Export LiveView: import form, progress, results,
|
Function components for the Import LiveView: import form, progress, results,
|
||||||
custom fields notice, and template links. Keeps the main LiveView focused on
|
custom fields notice, and template links. Keeps the main LiveView focused on
|
||||||
mount/handle_event/handle_info and glue code.
|
mount/handle_event/handle_info and glue code.
|
||||||
"""
|
"""
|
||||||
|
|
@ -180,6 +180,67 @@
|
||||||
>
|
>
|
||||||
{member.email}
|
{member.email}
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col
|
||||||
|
:let={member}
|
||||||
|
:if={:join_date in @member_fields_visible}
|
||||||
|
label={
|
||||||
|
~H"""
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
|
id={:sort_join_date}
|
||||||
|
field={:join_date}
|
||||||
|
label={gettext("Join Date")}
|
||||||
|
sort_field={@sort_field}
|
||||||
|
sort_order={@sort_order}
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{MvWeb.MemberLive.Index.format_date(member.join_date)}
|
||||||
|
</:col>
|
||||||
|
<:col
|
||||||
|
:let={member}
|
||||||
|
:if={:exit_date in @member_fields_visible}
|
||||||
|
label={
|
||||||
|
~H"""
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
|
id={:sort_exit_date}
|
||||||
|
field={:exit_date}
|
||||||
|
label={gettext("Exit Date")}
|
||||||
|
sort_field={@sort_field}
|
||||||
|
sort_order={@sort_order}
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{MvWeb.MemberLive.Index.format_date(member.exit_date)}
|
||||||
|
</:col>
|
||||||
|
<:col
|
||||||
|
:let={member}
|
||||||
|
:if={:notes in @member_fields_visible}
|
||||||
|
label={gettext("Notes")}
|
||||||
|
>
|
||||||
|
{member.notes}
|
||||||
|
</:col>
|
||||||
|
<:col
|
||||||
|
:let={member}
|
||||||
|
:if={:city in @member_fields_visible}
|
||||||
|
label={
|
||||||
|
~H"""
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
|
id={:sort_city}
|
||||||
|
field={:city}
|
||||||
|
label={gettext("City")}
|
||||||
|
sort_field={@sort_field}
|
||||||
|
sort_order={@sort_order}
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{member.city}
|
||||||
|
</:col>
|
||||||
<:col
|
<:col
|
||||||
:let={member}
|
:let={member}
|
||||||
:if={:street in @member_fields_visible}
|
:if={:street in @member_fields_visible}
|
||||||
|
|
@ -236,57 +297,21 @@
|
||||||
</:col>
|
</:col>
|
||||||
<:col
|
<:col
|
||||||
:let={member}
|
:let={member}
|
||||||
:if={:city in @member_fields_visible}
|
:if={:membership_fee_start_date in @member_fields_visible}
|
||||||
label={
|
label={
|
||||||
~H"""
|
~H"""
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MvWeb.Components.SortHeaderComponent}
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
id={:sort_city}
|
id={:sort_membership_fee_start_date}
|
||||||
field={:city}
|
field={:membership_fee_start_date}
|
||||||
label={gettext("City")}
|
label={gettext("Membership Fee Start Date")}
|
||||||
sort_field={@sort_field}
|
sort_field={@sort_field}
|
||||||
sort_order={@sort_order}
|
sort_order={@sort_order}
|
||||||
/>
|
/>
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{member.city}
|
{MvWeb.MemberLive.Index.format_date(member.membership_fee_start_date)}
|
||||||
</:col>
|
|
||||||
<:col
|
|
||||||
:let={member}
|
|
||||||
:if={:join_date in @member_fields_visible}
|
|
||||||
label={
|
|
||||||
~H"""
|
|
||||||
<.live_component
|
|
||||||
module={MvWeb.Components.SortHeaderComponent}
|
|
||||||
id={:sort_join_date}
|
|
||||||
field={:join_date}
|
|
||||||
label={gettext("Join Date")}
|
|
||||||
sort_field={@sort_field}
|
|
||||||
sort_order={@sort_order}
|
|
||||||
/>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{MvWeb.MemberLive.Index.format_date(member.join_date)}
|
|
||||||
</:col>
|
|
||||||
<:col
|
|
||||||
:let={member}
|
|
||||||
:if={:exit_date in @member_fields_visible}
|
|
||||||
label={
|
|
||||||
~H"""
|
|
||||||
<.live_component
|
|
||||||
module={MvWeb.Components.SortHeaderComponent}
|
|
||||||
id={:sort_exit_date}
|
|
||||||
field={:exit_date}
|
|
||||||
label={gettext("Exit Date")}
|
|
||||||
sort_field={@sort_field}
|
|
||||||
sort_order={@sort_order}
|
|
||||||
/>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{MvWeb.MemberLive.Index.format_date(member.exit_date)}
|
|
||||||
</:col>
|
</:col>
|
||||||
<:col
|
<:col
|
||||||
:let={member}
|
:let={member}
|
||||||
|
|
@ -322,7 +347,6 @@
|
||||||
<%= for group <- (member.groups || []) do %>
|
<%= for group <- (member.groups || []) do %>
|
||||||
<span
|
<span
|
||||||
class="badge badge-outline badge-primary"
|
class="badge badge-outline badge-primary"
|
||||||
role="status"
|
|
||||||
aria-label={gettext("Member of group %{name}", name: group.name)}
|
aria-label={gettext("Member of group %{name}", name: group.name)}
|
||||||
>
|
>
|
||||||
{group.name}
|
{group.name}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
## Sections
|
## Sections
|
||||||
- Personal Data: Name, address, contact information, membership dates, notes
|
- Personal Data: Name, address, contact information, membership dates, notes
|
||||||
- Custom Fields: Dynamic fields in uniform grid layout (sorted by name)
|
- Custom Fields: Dynamic fields in uniform grid layout (sorted by name)
|
||||||
|
- Groups: Links to group detail pages in Personal Data section
|
||||||
|
- Payment Data: Membership fee type and cycle status
|
||||||
- Membership Fees: Tab showing all membership fee cycles with status management (via MembershipFeesComponent)
|
- Membership Fees: Tab showing all membership fee cycles with status management (via MembershipFeesComponent)
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
@ -146,6 +148,28 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%!-- Groups (in Personal Data) --%>
|
||||||
|
<% groups = @member.groups || [] %>
|
||||||
|
<div>
|
||||||
|
<.data_field label={gettext("Groups")}>
|
||||||
|
<%= if Enum.empty?(groups) do %>
|
||||||
|
<span class="text-base-content/70 italic">{gettext("No groups")}</span>
|
||||||
|
<% else %>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<%= for group <- groups do %>
|
||||||
|
<.link
|
||||||
|
navigate={~p"/groups/#{group.slug}"}
|
||||||
|
class="btn btn-xs btn-outline btn-primary"
|
||||||
|
aria-label={gettext("Member of group %{name}", name: group.name)}
|
||||||
|
>
|
||||||
|
{group.name}
|
||||||
|
</.link>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</.data_field>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Notes --%>
|
<%!-- Notes --%>
|
||||||
<%= if @member.notes && String.trim(@member.notes) != "" do %>
|
<%= if @member.notes && String.trim(@member.notes) != "" do %>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -262,7 +286,8 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
:user,
|
:user,
|
||||||
:membership_fee_type,
|
:membership_fee_type,
|
||||||
custom_field_values: [:custom_field],
|
custom_field_values: [:custom_field],
|
||||||
membership_fee_cycles: [:membership_fee_type]
|
membership_fee_cycles: [:membership_fee_type],
|
||||||
|
groups: [:id, :name, :slug]
|
||||||
])
|
])
|
||||||
|
|
||||||
member = Ash.read_one!(query, actor: actor)
|
member = Ash.read_one!(query, actor: actor)
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ defmodule MvWeb.Router do
|
||||||
live "/admin/roles/:id", RoleLive.Show, :show
|
live "/admin/roles/:id", RoleLive.Show, :show
|
||||||
live "/admin/roles/:id/edit", RoleLive.Form, :edit
|
live "/admin/roles/:id/edit", RoleLive.Form, :edit
|
||||||
|
|
||||||
# Import/Export (Admin only)
|
# Import (Admin only)
|
||||||
live "/admin/import-export", ImportExportLive
|
live "/admin/import", ImportLive
|
||||||
|
|
||||||
post "/members/export.csv", MemberExportController, :export
|
post "/members/export.csv", MemberExportController, :export
|
||||||
post "/members/export.pdf", MemberPdfExportController, :export
|
post "/members/export.pdf", MemberPdfExportController, :export
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ msgid "House Number"
|
||||||
msgstr "Hausnummer"
|
msgstr "Hausnummer"
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -1542,6 +1543,7 @@ msgstr "Du bist dabei alle %{count} Zyklen für dieses Mitglied zu löschen."
|
||||||
msgid "Delete Membership Fee Type"
|
msgid "Delete Membership Fee Type"
|
||||||
msgstr "Mitgliedsbeitragsart löschen"
|
msgstr "Mitgliedsbeitragsart löschen"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Membership Fee Start Date"
|
msgid "Membership Fee Start Date"
|
||||||
|
|
@ -1859,7 +1861,7 @@ msgstr "erstellt"
|
||||||
msgid "updated"
|
msgid "updated"
|
||||||
msgstr "aktualisiert"
|
msgstr "aktualisiert"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
|
|
@ -1980,37 +1982,37 @@ msgstr "Bezahlstatus"
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr "Zurücksetzen"
|
msgstr "Zurücksetzen"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid " (Field: %{field})"
|
msgid " (Field: %{field})"
|
||||||
msgstr " (Datenfeld: %{field})"
|
msgstr " (Datenfeld: %{field})"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "CSV File"
|
msgid "CSV File"
|
||||||
msgstr "CSV Datei"
|
msgstr "CSV Datei"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Download CSV templates:"
|
msgid "Download CSV templates:"
|
||||||
msgstr "CSV Vorlagen herunterladen:"
|
msgstr "CSV Vorlagen herunterladen:"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "English Template"
|
msgid "English Template"
|
||||||
msgstr "Englische Vorlage"
|
msgstr "Englische Vorlage"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Error list truncated to %{count} entries"
|
msgid "Error list truncated to %{count} entries"
|
||||||
msgstr "Liste der Fehler auf %{count} Einträge reduziert"
|
msgstr "Liste der Fehler auf %{count} Einträge reduziert"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Errors"
|
msgid "Errors"
|
||||||
msgstr "Fehler"
|
msgstr "Fehler"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed to prepare CSV import: %{reason}"
|
msgid "Failed to prepare CSV import: %{reason}"
|
||||||
msgstr "Das Vorbereiten des CSV Imports ist gescheitert: %{reason}"
|
msgstr "Das Vorbereiten des CSV Imports ist gescheitert: %{reason}"
|
||||||
|
|
@ -2025,27 +2027,27 @@ msgstr "Das Importieren von %{idx} ist gescheitert: %{reason}"
|
||||||
msgid "Failed to read file: %{reason}"
|
msgid "Failed to read file: %{reason}"
|
||||||
msgstr "Fehler beim Lesen der Datei: %{reason}"
|
msgstr "Fehler beim Lesen der Datei: %{reason}"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed: %{count} row(s)"
|
msgid "Failed: %{count} row(s)"
|
||||||
msgstr "Fehlgeschlagen: %{count} Zeile(n)"
|
msgstr "Fehlgeschlagen: %{count} Zeile(n)"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "German Template"
|
msgid "German Template"
|
||||||
msgstr "Deutsche Vorlage"
|
msgstr "Deutsche Vorlage"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Members (CSV)"
|
msgid "Import Members (CSV)"
|
||||||
msgstr "Mitglieder importieren (CSV)"
|
msgstr "Mitglieder importieren (CSV)"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Results"
|
msgid "Import Results"
|
||||||
msgstr "Import-Ergebnisse"
|
msgstr "Import-Ergebnisse"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import is already running. Please wait for it to complete."
|
msgid "Import is already running. Please wait for it to complete."
|
||||||
msgstr "Import läuft bereits. Bitte warte, bis er abgeschlossen ist."
|
msgstr "Import läuft bereits. Bitte warte, bis er abgeschlossen ist."
|
||||||
|
|
@ -2060,7 +2062,7 @@ msgstr "Import-Status fehlt. Chunk %{idx} kann nicht verarbeitet werden."
|
||||||
msgid "Invalid chunk index: %{idx}"
|
msgid "Invalid chunk index: %{idx}"
|
||||||
msgstr "Ungültiger Chunk-Index: %{idx}"
|
msgstr "Ungültiger Chunk-Index: %{idx}"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Line %{line}: %{message}"
|
msgid "Line %{line}: %{message}"
|
||||||
msgstr "Zeile %{line}: %{message}"
|
msgstr "Zeile %{line}: %{message}"
|
||||||
|
|
@ -2070,47 +2072,47 @@ msgstr "Zeile %{line}: %{message}"
|
||||||
msgid "No file was uploaded"
|
msgid "No file was uploaded"
|
||||||
msgstr "Es wurde keine Datei hochgeladen"
|
msgstr "Es wurde keine Datei hochgeladen"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Only administrators can import members from CSV files."
|
msgid "Only administrators can import members from CSV files."
|
||||||
msgstr "Nur Administrator*innen können Mitglieder aus CSV-Dateien importieren."
|
msgstr "Nur Administrator*innen können Mitglieder aus CSV-Dateien importieren."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Please select a CSV file to import."
|
msgid "Please select a CSV file to import."
|
||||||
msgstr "Bitte wähle eine CSV-Datei zum Importieren."
|
msgstr "Bitte wähle eine CSV-Datei zum Importieren."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Please wait for the file upload to complete before starting the import."
|
msgid "Please wait for the file upload to complete before starting the import."
|
||||||
msgstr "Bitte warte, bis der Datei-Upload abgeschlossen ist, bevor du den Import startest."
|
msgstr "Bitte warte, bis der Datei-Upload abgeschlossen ist, bevor du den Import startest."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Processing chunk %{current} of %{total}..."
|
msgid "Processing chunk %{current} of %{total}..."
|
||||||
msgstr "Verarbeite Chunk %{current} von %{total}..."
|
msgstr "Verarbeite Chunk %{current} von %{total}..."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Start Import"
|
msgid "Start Import"
|
||||||
msgstr "Import starten"
|
msgstr "Import starten"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Starting import..."
|
msgid "Starting import..."
|
||||||
msgstr "Import wird gestartet..."
|
msgstr "Import wird gestartet..."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Successfully inserted: %{count} member(s)"
|
msgid "Successfully inserted: %{count} member(s)"
|
||||||
msgstr "Erfolgreich eingefügt: %{count} Mitglied(er)"
|
msgstr "Erfolgreich eingefügt: %{count} Mitglied(er)"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
msgstr "Zusammenfassung"
|
msgstr "Zusammenfassung"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Warnings"
|
msgid "Warnings"
|
||||||
msgstr "Warnungen"
|
msgstr "Warnungen"
|
||||||
|
|
@ -2202,11 +2204,13 @@ msgstr "Gruppe erfolgreich gespeichert."
|
||||||
#: lib/mv_web/live/components/member_filter_component.ex
|
#: lib/mv_web/live/components/member_filter_component.ex
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppen"
|
||||||
|
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No groups"
|
msgid "No groups"
|
||||||
msgstr "Keine Gruppen"
|
msgstr "Keine Gruppen"
|
||||||
|
|
@ -2325,7 +2329,7 @@ msgstr "%{name} entfernen"
|
||||||
msgid "Some members could not be added: %{errors}"
|
msgid "Some members could not be added: %{errors}"
|
||||||
msgstr "Einige Mitglieder konnten nicht hinzugefügt werden: %{errors}"
|
msgstr "Einige Mitglieder konnten nicht hinzugefügt werden: %{errors}"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "CSV files only, maximum %{size} MB"
|
msgid "CSV files only, maximum %{size} MB"
|
||||||
msgstr "Nur CSV Dateien, maximal %{size} MB"
|
msgstr "Nur CSV Dateien, maximal %{size} MB"
|
||||||
|
|
@ -2355,43 +2359,22 @@ msgstr "Datenfeld: %{name} – erwartet %{type}, erhalten: %{value}"
|
||||||
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
||||||
msgstr "Unbekannte Spalte '%{header}' wird ignoriert. Falls dies ein Datenfeld ist, erstelle es in Mila vor dem Import."
|
msgstr "Unbekannte Spalte '%{header}' wird ignoriert. Falls dies ein Datenfeld ist, erstelle es in Mila vor dem Import."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
|
||||||
msgid "Export Members (CSV)"
|
|
||||||
msgstr "Mitglieder importieren (CSV)"
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Export functionality will be available in a future release."
|
|
||||||
msgstr "Export-Funktionalität ist im nächsten release verfügbar."
|
|
||||||
|
|
||||||
#: lib/mv/membership/import/import_runner.ex
|
#: lib/mv/membership/import/import_runner.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Failed to read uploaded file: unexpected format"
|
msgid "Failed to read uploaded file: unexpected format"
|
||||||
msgstr "Fehler beim Lesen der hochgeladenen Datei"
|
msgstr "Fehler beim Lesen der hochgeladenen Datei"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import members from CSV files or export member data."
|
|
||||||
msgstr "Importiere Mitglieder aus CSV-Dateien oder exportiere Mitgliederdaten."
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/sidebar.ex
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import/Export"
|
|
||||||
msgstr "Import/Export"
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "You do not have permission to access this page."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr "Du hast keine Berechtigung, auf diese Seite zuzugreifen."
|
msgstr "Du hast keine Berechtigung, auf diese Seite zuzugreifen."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Manage Member Data"
|
msgid "Manage Member Data"
|
||||||
msgstr "Mitgliederdaten verwalten"
|
msgstr "Mitgliederdaten verwalten"
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
||||||
msgstr "Verwende die Namen der Datenfelder als Spaltennamen in der CSV Datei. Datenfelder müssen in Mila bereits angelegt sein, damit sie importiert werden können. sie müssen in der Liste der Mitgliederdaten als Datenfeld enthalten sein (z.B. E-Mail). Spalten mit unbekannten Spaltenüberschriften werden mit einer Warnung ignoriert."
|
msgstr "Verwende die Namen der Datenfelder als Spaltennamen in der CSV Datei. Datenfelder müssen in Mila bereits angelegt sein, damit sie importiert werden können. sie müssen in der Liste der Mitgliederdaten als Datenfeld enthalten sein (z.B. E-Mail). Spalten mit unbekannten Spaltenüberschriften werden mit einer Warnung ignoriert."
|
||||||
|
|
@ -2453,7 +2436,7 @@ msgstr "SSO / OIDC Nutzer*in"
|
||||||
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
msgstr "Diese*r Nutzer*in ist über SSO (Single Sign-On) verbunden. Ein hier festgelegtes oder geändertes Passwort wirkt sich nur auf die Anmeldung mit E-Mail-Adresse und Passwort in dieser Anwendung aus. Es ändert nicht das Passwort in Eurem Identitätsanbieter (z. B. Authentik). Um das SSO-Passwort zu ändern, wende dich an den Identitätsanbieter oder die IT-Abteilung Ihrer Organisation."
|
msgstr "Diese*r Nutzer*in ist über SSO (Single Sign-On) verbunden. Ein hier festgelegtes oder geändertes Passwort wirkt sich nur auf die Anmeldung mit E-Mail-Adresse und Passwort in dieser Anwendung aus. Es ändert nicht das Passwort in Eurem Identitätsanbieter (z. B. Authentik). Um das SSO-Passwort zu ändern, wende dich an den Identitätsanbieter oder die IT-Abteilung Ihrer Organisation."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import aborted"
|
msgid "Import aborted"
|
||||||
msgstr "Import abgebrochen"
|
msgstr "Import abgebrochen"
|
||||||
|
|
@ -2474,6 +2457,7 @@ msgid "unpaid"
|
||||||
msgstr "Unbezahlt"
|
msgstr "Unbezahlt"
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Member of group %{name}"
|
msgid "Member of group %{name}"
|
||||||
msgstr "Mitglied der Gruppe %{name}"
|
msgstr "Mitglied der Gruppe %{name}"
|
||||||
|
|
@ -2614,11 +2598,23 @@ msgstr "Anzahl Mitglieder:"
|
||||||
msgid "PDF"
|
msgid "PDF"
|
||||||
msgstr "PDF"
|
msgstr "PDF"
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/global_settings_live.ex
|
#: lib/mv_web/components/layouts/sidebar.ex
|
||||||
#~ #, elixir-autogen, elixir-format, fuzzy
|
#: lib/mv_web/live/import_live.ex
|
||||||
#~ msgid "Custom Fields in CSV Import"
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
#~ msgstr "Benutzerdefinierte Felder"
|
msgid "Import"
|
||||||
|
msgstr "Import"
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format, fuzzy
|
||||||
|
#~ msgid "Export Members (CSV)"
|
||||||
|
#~ msgstr "Mitglieder exportieren (CSV)"
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Failed to prepare CSV import: %{error}"
|
#~ msgid "Export functionality will be available in a future release."
|
||||||
#~ msgstr "Das Vorbereiten des CSV Imports ist gescheitert: %{error}"
|
#~ msgstr "Export-Funktionalität ist im nächsten release verfügbar."
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Import members from CSV files or export member data."
|
||||||
|
#~ msgstr "Importiere Mitglieder aus CSV-Dateien oder exportiere Mitgliederdaten."
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ msgid "House Number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -1543,6 +1544,7 @@ msgstr ""
|
||||||
msgid "Delete Membership Fee Type"
|
msgid "Delete Membership Fee Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Membership Fee Start Date"
|
msgid "Membership Fee Start Date"
|
||||||
|
|
@ -1860,7 +1862,7 @@ msgstr ""
|
||||||
msgid "updated"
|
msgid "updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
|
|
@ -1981,37 +1983,37 @@ msgstr ""
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid " (Field: %{field})"
|
msgid " (Field: %{field})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "CSV File"
|
msgid "CSV File"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Download CSV templates:"
|
msgid "Download CSV templates:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "English Template"
|
msgid "English Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Error list truncated to %{count} entries"
|
msgid "Error list truncated to %{count} entries"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Errors"
|
msgid "Errors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed to prepare CSV import: %{reason}"
|
msgid "Failed to prepare CSV import: %{reason}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2026,27 +2028,27 @@ msgstr ""
|
||||||
msgid "Failed to read file: %{reason}"
|
msgid "Failed to read file: %{reason}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed: %{count} row(s)"
|
msgid "Failed: %{count} row(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "German Template"
|
msgid "German Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Members (CSV)"
|
msgid "Import Members (CSV)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Results"
|
msgid "Import Results"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import is already running. Please wait for it to complete."
|
msgid "Import is already running. Please wait for it to complete."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2061,7 +2063,7 @@ msgstr ""
|
||||||
msgid "Invalid chunk index: %{idx}"
|
msgid "Invalid chunk index: %{idx}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Line %{line}: %{message}"
|
msgid "Line %{line}: %{message}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2071,47 +2073,47 @@ msgstr ""
|
||||||
msgid "No file was uploaded"
|
msgid "No file was uploaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Only administrators can import members from CSV files."
|
msgid "Only administrators can import members from CSV files."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Please select a CSV file to import."
|
msgid "Please select a CSV file to import."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Please wait for the file upload to complete before starting the import."
|
msgid "Please wait for the file upload to complete before starting the import."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Processing chunk %{current} of %{total}..."
|
msgid "Processing chunk %{current} of %{total}..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Start Import"
|
msgid "Start Import"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Starting import..."
|
msgid "Starting import..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Successfully inserted: %{count} member(s)"
|
msgid "Successfully inserted: %{count} member(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Warnings"
|
msgid "Warnings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2203,11 +2205,13 @@ msgstr ""
|
||||||
#: lib/mv_web/live/components/member_filter_component.ex
|
#: lib/mv_web/live/components/member_filter_component.ex
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No groups"
|
msgid "No groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2326,7 +2330,7 @@ msgstr ""
|
||||||
msgid "Some members could not be added: %{errors}"
|
msgid "Some members could not be added: %{errors}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "CSV files only, maximum %{size} MB"
|
msgid "CSV files only, maximum %{size} MB"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2356,43 +2360,22 @@ msgstr ""
|
||||||
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Export Members (CSV)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Export functionality will be available in a future release."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv/membership/import/import_runner.ex
|
#: lib/mv/membership/import/import_runner.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed to read uploaded file: unexpected format"
|
msgid "Failed to read uploaded file: unexpected format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import members from CSV files or export member data."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/sidebar.ex
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import/Export"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "You do not have permission to access this page."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Manage Member Data"
|
msgid "Manage Member Data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2454,7 +2437,7 @@ msgstr ""
|
||||||
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import aborted"
|
msgid "Import aborted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2475,6 +2458,7 @@ msgid "unpaid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Member of group %{name}"
|
msgid "Member of group %{name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2614,3 +2598,9 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "PDF"
|
msgid "PDF"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/components/layouts/sidebar.ex
|
||||||
|
#: lib/mv_web/live/import_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Import"
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ msgid "House Number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -1543,6 +1544,7 @@ msgstr ""
|
||||||
msgid "Delete Membership Fee Type"
|
msgid "Delete Membership Fee Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/translations/member_fields.ex
|
#: lib/mv_web/translations/member_fields.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Membership Fee Start Date"
|
msgid "Membership Fee Start Date"
|
||||||
|
|
@ -1860,7 +1862,7 @@ msgstr ""
|
||||||
msgid "updated"
|
msgid "updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
|
|
@ -1981,37 +1983,37 @@ msgstr ""
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid " (Field: %{field})"
|
msgid " (Field: %{field})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "CSV File"
|
msgid "CSV File"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Download CSV templates:"
|
msgid "Download CSV templates:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "English Template"
|
msgid "English Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Error list truncated to %{count} entries"
|
msgid "Error list truncated to %{count} entries"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Errors"
|
msgid "Errors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed to prepare CSV import: %{reason}"
|
msgid "Failed to prepare CSV import: %{reason}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2026,27 +2028,27 @@ msgstr ""
|
||||||
msgid "Failed to read file: %{reason}"
|
msgid "Failed to read file: %{reason}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed: %{count} row(s)"
|
msgid "Failed: %{count} row(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "German Template"
|
msgid "German Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Members (CSV)"
|
msgid "Import Members (CSV)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import Results"
|
msgid "Import Results"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import is already running. Please wait for it to complete."
|
msgid "Import is already running. Please wait for it to complete."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2061,7 +2063,7 @@ msgstr ""
|
||||||
msgid "Invalid chunk index: %{idx}"
|
msgid "Invalid chunk index: %{idx}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Line %{line}: %{message}"
|
msgid "Line %{line}: %{message}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2071,47 +2073,47 @@ msgstr ""
|
||||||
msgid "No file was uploaded"
|
msgid "No file was uploaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Only administrators can import members from CSV files."
|
msgid "Only administrators can import members from CSV files."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Please select a CSV file to import."
|
msgid "Please select a CSV file to import."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Please wait for the file upload to complete before starting the import."
|
msgid "Please wait for the file upload to complete before starting the import."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Processing chunk %{current} of %{total}..."
|
msgid "Processing chunk %{current} of %{total}..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Start Import"
|
msgid "Start Import"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Starting import..."
|
msgid "Starting import..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Successfully inserted: %{count} member(s)"
|
msgid "Successfully inserted: %{count} member(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Warnings"
|
msgid "Warnings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2203,11 +2205,13 @@ msgstr ""
|
||||||
#: lib/mv_web/live/components/member_filter_component.ex
|
#: lib/mv_web/live/components/member_filter_component.ex
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/group_live/index.ex
|
#: lib/mv_web/live/group_live/index.ex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "No groups"
|
msgid "No groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2326,7 +2330,7 @@ msgstr ""
|
||||||
msgid "Some members could not be added: %{errors}"
|
msgid "Some members could not be added: %{errors}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "CSV files only, maximum %{size} MB"
|
msgid "CSV files only, maximum %{size} MB"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2356,43 +2360,22 @@ msgstr ""
|
||||||
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
||||||
msgstr "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
msgstr "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing."
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
|
||||||
msgid "Export Members (CSV)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Export functionality will be available in a future release."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv/membership/import/import_runner.ex
|
#: lib/mv/membership/import/import_runner.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Failed to read uploaded file: unexpected format"
|
msgid "Failed to read uploaded file: unexpected format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
#: lib/mv_web/live/import_live.ex
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import members from CSV files or export member data."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/sidebar.ex
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Import/Export"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live.ex
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "You do not have permission to access this page."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Manage Member Data"
|
msgid "Manage Member Data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of member data (like e-mail or first name). Unknown data field columns will be ignored with a warning."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2454,7 +2437,7 @@ msgstr ""
|
||||||
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/import_export_live/components.ex
|
#: lib/mv_web/live/import_live/components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Import aborted"
|
msgid "Import aborted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2475,6 +2458,7 @@ msgid "unpaid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Member of group %{name}"
|
msgid "Member of group %{name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2615,7 +2599,23 @@ msgstr "Member count:"
|
||||||
msgid "PDF"
|
msgid "PDF"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/global_settings_live.ex
|
#: lib/mv_web/components/layouts/sidebar.ex
|
||||||
|
#: lib/mv_web/live/import_live.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Import"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
#~ #, elixir-autogen, elixir-format, fuzzy
|
#~ #, elixir-autogen, elixir-format, fuzzy
|
||||||
#~ msgid "Custom Fields in CSV Import"
|
#~ msgid "Export Members (CSV)"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Export functionality will be available in a future release."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/import_export_live.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Import members from CSV files or export member data."
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ defmodule MvWeb.GlobalSettingsLiveConfigTest do
|
||||||
# Arrange: Set custom row limit to 500
|
# Arrange: Set custom row limit to 500
|
||||||
Application.put_env(:mv, :csv_import, max_rows: 500)
|
Application.put_env(:mv, :csv_import, max_rows: 500)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
|
|
||||||
# Generate CSV with 501 rows (exceeding custom limit of 500)
|
# Generate CSV with 501 rows (exceeding custom limit of 500)
|
||||||
header = "first_name;last_name;email;street;postal_code;city\n"
|
header = "first_name;last_name;email;street;postal_code;city\n"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule MvWeb.ImportExportLiveTest do
|
defmodule MvWeb.ImportLiveTest do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Tests for Import/Export LiveView: authorization (business rule), CSV import integration,
|
Tests for Import LiveView: authorization (business rule), CSV import integration,
|
||||||
and minimal UI smoke tests. CSV parsing/validation logic is covered by
|
and minimal UI smoke tests. CSV parsing/validation logic is covered by
|
||||||
Mv.Membership.Import.MemberCSVTest; here we verify access control and end-to-end outcomes.
|
Mv.Membership.Import.MemberCSVTest; here we verify access control and end-to-end outcomes.
|
||||||
"""
|
"""
|
||||||
|
|
@ -31,7 +31,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
|
|
||||||
# ---------- Business logic: Authorization ----------
|
# ---------- Business logic: Authorization ----------
|
||||||
describe "Authorization" do
|
describe "Authorization" do
|
||||||
test "non-admin user cannot access import/export page and sees permission error", %{
|
test "non-admin user cannot access import page and sees permission error", %{
|
||||||
conn: conn
|
conn: conn
|
||||||
} do
|
} do
|
||||||
member_user = Mv.Fixtures.user_with_role_fixture("own_data")
|
member_user = Mv.Fixtures.user_with_role_fixture("own_data")
|
||||||
|
|
@ -42,7 +42,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
|> put_locale_en()
|
|> put_locale_en()
|
||||||
|
|
||||||
assert {:error, {:redirect, %{to: redirect_path, flash: %{"error" => msg}}}} =
|
assert {:error, {:redirect, %{to: redirect_path, flash: %{"error" => msg}}}} =
|
||||||
live(conn, ~p"/admin/import-export")
|
live(conn, ~p"/admin/import")
|
||||||
|
|
||||||
assert redirect_path =~ "/users/"
|
assert redirect_path =~ "/users/"
|
||||||
assert msg =~ "don't have permission"
|
assert msg =~ "don't have permission"
|
||||||
|
|
@ -55,7 +55,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
||||||
|> File.read!()
|
|> File.read!()
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, csv_content)
|
upload_csv_file(view, csv_content)
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
wait_for_import_completion()
|
wait_for_import_completion()
|
||||||
|
|
@ -109,7 +109,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "invalid CSV shows user-friendly prepare error", %{conn: conn} do
|
test "invalid CSV shows user-friendly prepare error", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, "invalid_header\nincomplete_row", "invalid.csv")
|
upload_csv_file(view, "invalid_header\nincomplete_row", "invalid.csv")
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
html = render(view)
|
html = render(view)
|
||||||
|
|
@ -120,7 +120,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
conn: conn,
|
conn: conn,
|
||||||
invalid_csv: csv_content
|
invalid_csv: csv_content
|
||||||
} do
|
} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, csv_content, "invalid_import.csv")
|
upload_csv_file(view, csv_content, "invalid_import.csv")
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
wait_for_import_completion()
|
wait_for_import_completion()
|
||||||
|
|
@ -135,7 +135,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "error list is capped and truncation message is shown", %{conn: conn} do
|
test "error list is capped and truncation message is shown", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
header = "first_name;last_name;email;street;postal_code;city\n"
|
header = "first_name;last_name;email;street;postal_code;city\n"
|
||||||
|
|
||||||
invalid_rows =
|
invalid_rows =
|
||||||
|
|
@ -153,7 +153,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "row limit is enforced (1001 rows rejected)", %{conn: conn} do
|
test "row limit is enforced (1001 rows rejected)", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
header = "first_name;last_name;email;street;postal_code;city\n"
|
header = "first_name;last_name;email;street;postal_code;city\n"
|
||||||
|
|
||||||
rows =
|
rows =
|
||||||
|
|
@ -168,7 +168,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "BOM and semicolon delimiter are accepted", %{conn: conn} do
|
test "BOM and semicolon delimiter are accepted", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
|
|
||||||
csv_content =
|
csv_content =
|
||||||
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_bom_semicolon.csv"])
|
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_bom_semicolon.csv"])
|
||||||
|
|
@ -187,7 +187,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
test "physical line numbers in errors (empty line does not shift numbering)", %{
|
test "physical line numbers in errors (empty line does not shift numbering)", %{
|
||||||
conn: conn
|
conn: conn
|
||||||
} do
|
} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
|
|
||||||
csv_content =
|
csv_content =
|
||||||
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_empty_lines.csv"])
|
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_empty_lines.csv"])
|
||||||
|
|
@ -207,7 +207,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
conn: conn,
|
conn: conn,
|
||||||
unknown_custom_field_csv: csv_content
|
unknown_custom_field_csv: csv_content
|
||||||
} do
|
} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, csv_content, "unknown_custom.csv")
|
upload_csv_file(view, csv_content, "unknown_custom.csv")
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
wait_for_import_completion()
|
wait_for_import_completion()
|
||||||
|
|
@ -220,7 +220,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
# ---------- UI (smoke / framework): tagged for exclusion from fast CI ----------
|
# ---------- UI (smoke / framework): tagged for exclusion from fast CI ----------
|
||||||
describe "Import/Export page UI" do
|
describe "Import page UI" do
|
||||||
@describetag :ui
|
@describetag :ui
|
||||||
setup %{conn: conn} do
|
setup %{conn: conn} do
|
||||||
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||||
|
|
@ -233,19 +233,17 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
{:ok, conn: conn}
|
{:ok, conn: conn}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "page loads and shows import form and export placeholder", %{conn: conn} do
|
test "page loads and shows import form", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
assert has_element?(view, "[data-testid='csv-upload-form']")
|
assert has_element?(view, "[data-testid='csv-upload-form']")
|
||||||
assert has_element?(view, "[data-testid='start-import-button']")
|
assert has_element?(view, "[data-testid='start-import-button']")
|
||||||
assert has_element?(view, "[data-testid='custom-fields-link']")
|
assert has_element?(view, "[data-testid='custom-fields-link']")
|
||||||
html = render(view)
|
html = render(view)
|
||||||
assert html =~ "Import Members (CSV)"
|
assert html =~ "Import Members (CSV)"
|
||||||
assert html =~ "Export Members (CSV)"
|
|
||||||
assert html =~ "Export functionality will be available"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "template links and file input are present", %{conn: conn} do
|
test "template links and file input are present", %{conn: conn} do
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
assert has_element?(view, "a[href*='/templates/member_import_en.csv']")
|
assert has_element?(view, "a[href*='/templates/member_import_en.csv']")
|
||||||
assert has_element?(view, "a[href*='/templates/member_import_de.csv']")
|
assert has_element?(view, "a[href*='/templates/member_import_de.csv']")
|
||||||
assert has_element?(view, "label[for='csv_file']")
|
assert has_element?(view, "label[for='csv_file']")
|
||||||
|
|
@ -258,7 +256,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
||||||
|> File.read!()
|
|> File.read!()
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, csv_content)
|
upload_csv_file(view, csv_content)
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
wait_for_import_completion()
|
wait_for_import_completion()
|
||||||
|
|
@ -273,7 +271,7 @@ defmodule MvWeb.ImportExportLiveTest do
|
||||||
@tag :skip
|
@tag :skip
|
||||||
test "empty CSV shows error", %{conn: conn} do
|
test "empty CSV shows error", %{conn: conn} do
|
||||||
conn = put_locale_en(conn)
|
conn = put_locale_en(conn)
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/import-export")
|
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||||
upload_csv_file(view, " ", "empty.csv")
|
upload_csv_file(view, " ", "empty.csv")
|
||||||
submit_import(view)
|
submit_import(view)
|
||||||
html = render(view)
|
html = render(view)
|
||||||
|
|
@ -3,7 +3,7 @@ defmodule MvWeb.MemberLive.IndexGroupsAccessibilityTest do
|
||||||
Tests for accessibility of groups feature in the member overview.
|
Tests for accessibility of groups feature in the member overview.
|
||||||
|
|
||||||
Tests cover:
|
Tests cover:
|
||||||
- Badges have role="status" and aria-label
|
- Badges have aria-label for group membership (no role="status"; reserved for live regions)
|
||||||
- Filter dropdown has aria-label
|
- Filter dropdown has aria-label
|
||||||
- Sort header has aria-label for screen reader
|
- Sort header has aria-label for screen reader
|
||||||
- Keyboard navigation works (Tab through filter, sort header)
|
- Keyboard navigation works (Tab through filter, sort header)
|
||||||
|
|
@ -44,7 +44,7 @@ defmodule MvWeb.MemberLive.IndexGroupsAccessibilityTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :ui
|
@tag :ui
|
||||||
test "group badges have role and aria-label", %{
|
test "group badges have aria-label for screen readers", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
member1: member1,
|
member1: member1,
|
||||||
group1: group1
|
group1: group1
|
||||||
|
|
@ -52,8 +52,8 @@ defmodule MvWeb.MemberLive.IndexGroupsAccessibilityTest do
|
||||||
conn = conn_with_oidc_user(conn)
|
conn = conn_with_oidc_user(conn)
|
||||||
{:ok, view, html} = live(conn, "/members")
|
{:ok, view, html} = live(conn, "/members")
|
||||||
|
|
||||||
# Verify badges have role="status" and aria-label containing the group name
|
# Verify badges have aria-label containing the group name (no role=status on badges)
|
||||||
assert has_element?(view, "span[role='status'][aria-label*='#{group1.name}']")
|
assert has_element?(view, "span[aria-label*='#{group1.name}']")
|
||||||
assert html =~ group1.name
|
assert html =~ group1.name
|
||||||
|
|
||||||
# Verify member1's row contains the badge
|
# Verify member1's row contains the badge
|
||||||
|
|
|
||||||
262
test/mv_web/member_live/show_groups_display_test.exs
Normal file
262
test/mv_web/member_live/show_groups_display_test.exs
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
defmodule MvWeb.MemberLive.ShowGroupsDisplayTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for displaying groups in the member detail view (Issue #374).
|
||||||
|
|
||||||
|
Tests cover:
|
||||||
|
- Groups in Personal Data (with and without groups)
|
||||||
|
- Group links with correct names and links to group detail pages
|
||||||
|
- Edge cases (one group, many groups)
|
||||||
|
- Security: groups visible only when user may view member
|
||||||
|
- Accessibility: group links have aria-label for screen readers
|
||||||
|
|
||||||
|
## Note on async
|
||||||
|
async: false to avoid PostgreSQL deadlocks when creating members and groups
|
||||||
|
in the same test run (same as IndexGroupsDisplayTest).
|
||||||
|
"""
|
||||||
|
use MvWeb.ConnCase, async: false
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
require Ash.Query
|
||||||
|
use Gettext, backend: MvWeb.Gettext
|
||||||
|
|
||||||
|
alias Mv.Membership.{Group, MemberGroup}
|
||||||
|
|
||||||
|
describe "groups section" do
|
||||||
|
setup do
|
||||||
|
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||||
|
|
||||||
|
{:ok, member} =
|
||||||
|
create_member(actor, %{
|
||||||
|
first_name: "Alice",
|
||||||
|
last_name: "Anderson",
|
||||||
|
email: "alice@example.com"
|
||||||
|
})
|
||||||
|
|
||||||
|
%{member: member, actor: actor}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays Groups section when member has at least one group", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
{:ok, group} = create_group(actor, "Board Members")
|
||||||
|
{:ok, _mg} = add_member_to_group(member, group, actor)
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
assert html =~ group.name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays all group links with correct names when member is in multiple groups", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
{:ok, group1} = create_group(actor, "Board Members")
|
||||||
|
{:ok, group2} = create_group(actor, "Active Members")
|
||||||
|
{:ok, _mg1} = add_member_to_group(member, group1, actor)
|
||||||
|
{:ok, _mg2} = add_member_to_group(member, group2, actor)
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
assert html =~ group1.name
|
||||||
|
assert html =~ group2.name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays Groups section when member has no groups (empty state)", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
# Groups are in Personal Data; label "Groups" and empty state "No groups" must be present
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
assert html =~ gettext("No groups")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders all group names when member has multiple groups", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
{:ok, group1} = create_group(actor, "Alpha")
|
||||||
|
{:ok, group2} = create_group(actor, "Beta")
|
||||||
|
{:ok, _mg1} = add_member_to_group(member, group1, actor)
|
||||||
|
{:ok, _mg2} = add_member_to_group(member, group2, actor)
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ "Alpha"
|
||||||
|
assert html =~ "Beta"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "groups section links" do
|
||||||
|
setup do
|
||||||
|
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||||
|
|
||||||
|
{:ok, member} =
|
||||||
|
create_member(actor, %{first_name: "Bob", last_name: "Brown", email: "bob@example.com"})
|
||||||
|
|
||||||
|
{:ok, group} = create_group(actor, "Board Members")
|
||||||
|
{:ok, _mg} = add_member_to_group(member, group, actor)
|
||||||
|
%{member: member, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "each group link goes to group detail page with correct slug", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
group: group
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
# Link to group detail: /groups/:slug (slug is URL-friendly, e.g. "board-members")
|
||||||
|
assert has_element?(view, "a[href*='/groups/#{group.slug}']", group.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "clicking group link navigates to group detail page", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
group: group
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
view
|
||||||
|
|> element("a[href*='/groups/#{group.slug}']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert_redirect(view, ~p"/groups/#{group.slug}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "groups section edge cases" do
|
||||||
|
setup do
|
||||||
|
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||||
|
|
||||||
|
{:ok, member} =
|
||||||
|
create_member(actor, %{
|
||||||
|
first_name: "Charlie",
|
||||||
|
last_name: "Clark",
|
||||||
|
email: "charlie@example.com"
|
||||||
|
})
|
||||||
|
|
||||||
|
%{member: member, actor: actor}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "member in exactly one group shows single link", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
{:ok, group} = create_group(actor, "Solo Group")
|
||||||
|
{:ok, _mg} = add_member_to_group(member, group, actor)
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
assert html =~ group.name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "member in many groups shows all group links", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
group_names = Enum.map(1..5, fn i -> "Group #{i}" end)
|
||||||
|
|
||||||
|
groups =
|
||||||
|
Enum.map(group_names, fn name ->
|
||||||
|
{:ok, g} = create_group(actor, name)
|
||||||
|
g
|
||||||
|
end)
|
||||||
|
|
||||||
|
for g <- groups do
|
||||||
|
{:ok, _mg} = add_member_to_group(member, g, actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
|
||||||
|
for name <- group_names do
|
||||||
|
assert html =~ name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "groups section with read_only user" do
|
||||||
|
@tag role: :read_only
|
||||||
|
test "user with read permission sees Groups section", %{conn: conn} do
|
||||||
|
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||||
|
|
||||||
|
{:ok, member} =
|
||||||
|
create_member(actor, %{
|
||||||
|
first_name: "Diana",
|
||||||
|
last_name: "Davis",
|
||||||
|
email: "diana@example.com"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, group} = create_group(actor, "Readers")
|
||||||
|
{:ok, _mg} = add_member_to_group(member, group, actor)
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ gettext("Groups")
|
||||||
|
assert html =~ group.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "groups section accessibility" do
|
||||||
|
setup do
|
||||||
|
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||||
|
|
||||||
|
{:ok, member} =
|
||||||
|
create_member(actor, %{first_name: "Eve", last_name: "Evans", email: "eve@example.com"})
|
||||||
|
|
||||||
|
{:ok, group} = create_group(actor, "A11y Group")
|
||||||
|
{:ok, _mg} = add_member_to_group(member, group, actor)
|
||||||
|
%{member: member, group: group}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "group links have aria-label for screen readers", %{
|
||||||
|
conn: conn,
|
||||||
|
member: member,
|
||||||
|
group: group
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, html} = live(conn, ~p"/members/#{member}")
|
||||||
|
|
||||||
|
assert html =~ group.name
|
||||||
|
|
||||||
|
# Group link has aria-label indicating group membership for screen readers
|
||||||
|
assert has_element?(view, "a[aria-label*='#{group.name}']")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helpers to reduce setup duplication (create member/group, assign member to group).
|
||||||
|
defp create_member(actor, attrs) do
|
||||||
|
Mv.Membership.create_member(attrs, actor: actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_group(actor, name) do
|
||||||
|
Group
|
||||||
|
|> Ash.Changeset.for_create(:create, %{name: name})
|
||||||
|
|> Ash.create(actor: actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_member_to_group(member, group, actor) do
|
||||||
|
MemberGroup
|
||||||
|
|> Ash.Changeset.for_create(:create, %{member_id: member.id, group_id: group.id})
|
||||||
|
|> Ash.create(actor: actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue