Compare commits
13 commits
b34d5ec99f
...
ae179703db
| Author | SHA1 | Date | |
|---|---|---|---|
| ae179703db | |||
| 422cf37a1e | |||
| a10d42f1ed | |||
| d1bab1288c | |||
| 1623b63207 | |||
| e6c5a58c65 | |||
| ee414c9440 | |||
| 366d4c104a | |||
| ce15b8f59b | |||
| d8384098b4 | |||
| ee094eec2f | |||
| eedd24b93c | |||
| 06ba50f05d |
32 changed files with 1023 additions and 721 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -41,3 +41,6 @@ npm-debug.log
|
||||||
.env
|
.env
|
||||||
|
|
||||||
.elixir_ls/
|
.elixir_ls/
|
||||||
|
|
||||||
|
# Docker secrets directory (generated by `just init-secrets`)
|
||||||
|
/secrets/
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- CopyToClipboard JavaScript hook with fallback for older browsers
|
- CopyToClipboard JavaScript hook with fallback for older browsers
|
||||||
- Button shows count of visible selected members (respects search/filter)
|
- Button shows count of visible selected members (respects search/filter)
|
||||||
- German/English translations
|
- German/English translations
|
||||||
|
- Docker secrets support via `_FILE` environment variables for all sensitive configuration (SECRET_KEY_BASE, TOKEN_SIGNING_SECRET, OIDC_CLIENT_SECRET, DATABASE_URL, DATABASE_PASSWORD)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Email validation false positive when linking user and member with identical emails (#168 Problem #4)
|
- Email validation false positive when linking user and member with identical emails (#168 Problem #4)
|
||||||
|
|
|
||||||
23
Justfile
23
Justfile
|
|
@ -91,3 +91,26 @@ remove-gettext-conflicts:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
find priv/gettext -type f -exec sed -i '/^<<<<<<< HEAD$/d; /^=======$/d; /^>>>>>>>/d' {} \;
|
find priv/gettext -type f -exec sed -i '/^<<<<<<< HEAD$/d; /^=======$/d; /^>>>>>>>/d' {} \;
|
||||||
|
|
||||||
|
# Production environment commands
|
||||||
|
# ================================
|
||||||
|
|
||||||
|
# Initialize secrets directory with generated secrets (only if not exists)
|
||||||
|
init-prod-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-prod-secrets
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
@ -217,6 +217,13 @@ For testing the production Docker build locally:
|
||||||
# OIDC_BASE_URL=http://localhost:8080/auth/v1
|
# OIDC_BASE_URL=http://localhost:8080/auth/v1
|
||||||
# OIDC_REDIRECT_URI=http://localhost:4001/auth/user/rauthy/callback
|
# OIDC_REDIRECT_URI=http://localhost:4001/auth/user/rauthy/callback
|
||||||
# OIDC_CLIENT_SECRET=<from-rauthy-client>
|
# OIDC_CLIENT_SECRET=<from-rauthy-client>
|
||||||
|
|
||||||
|
# Alternative: Use _FILE variables for Docker secrets (takes priority over regular vars):
|
||||||
|
# SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
|
||||||
|
# TOKEN_SIGNING_SECRET_FILE=/run/secrets/token_signing_secret
|
||||||
|
# OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret
|
||||||
|
# DATABASE_URL_FILE=/run/secrets/database_url
|
||||||
|
# DATABASE_PASSWORD_FILE=/run/secrets/database_password
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Start development environment** (for Rauthy):
|
3. **Start development environment** (for Rauthy):
|
||||||
|
|
@ -250,7 +257,7 @@ For actual production deployment:
|
||||||
- Set `OIDC_BASE_URL` to your production OIDC provider
|
- Set `OIDC_BASE_URL` to your production OIDC provider
|
||||||
- Configure proper Docker networks
|
- Configure proper Docker networks
|
||||||
3. **Set up SSL/TLS** (e.g., via reverse proxy like Nginx/Traefik)
|
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**
|
5. **Configure database backups**
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,75 @@ import Config
|
||||||
# any compile-time configuration in here, as it won't be applied.
|
# any compile-time configuration in here, as it won't be applied.
|
||||||
# The block below contains prod specific runtime configuration.
|
# 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_trailing(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
|
# ## Using releases
|
||||||
#
|
#
|
||||||
# If you use `mix release`, you need to explicitly enable the server
|
# If you use `mix release`, you need to explicitly enable the server
|
||||||
|
|
@ -21,12 +90,7 @@ if System.get_env("PHX_SERVER") do
|
||||||
end
|
end
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
database_url =
|
database_url = build_database_url.()
|
||||||
System.get_env("DATABASE_URL") ||
|
|
||||||
raise """
|
|
||||||
environment variable DATABASE_URL is missing.
|
|
||||||
For example: ecto://USER:PASS@HOST/DATABASE
|
|
||||||
"""
|
|
||||||
|
|
||||||
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
|
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
|
# 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
|
# to check this value into version control, so we use an environment
|
||||||
# variable instead.
|
# variable instead.
|
||||||
|
# Supports SECRET_KEY_BASE or SECRET_KEY_BASE_FILE for Docker secrets.
|
||||||
secret_key_base =
|
secret_key_base =
|
||||||
System.get_env("SECRET_KEY_BASE") ||
|
get_env_or_file!.("SECRET_KEY_BASE", """
|
||||||
raise """
|
environment variable SECRET_KEY_BASE (or SECRET_KEY_BASE_FILE) is missing.
|
||||||
environment variable SECRET_KEY_BASE is missing.
|
You can generate one by calling: mix phx.gen.secret
|
||||||
You can generate one by calling: mix phx.gen.secret
|
""")
|
||||||
"""
|
|
||||||
|
|
||||||
host = System.get_env("PHX_HOST") || raise "Please define the PHX_HOST environment variable."
|
host = System.get_env("PHX_HOST") || raise "Please define the PHX_HOST environment variable."
|
||||||
port = String.to_integer(System.get_env("PORT") || "4000")
|
port = String.to_integer(System.get_env("PORT") || "4000")
|
||||||
|
|
@ -54,32 +118,47 @@ if config_env() == :prod do
|
||||||
config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
||||||
|
|
||||||
# Rauthy OIDC configuration
|
# Rauthy OIDC configuration
|
||||||
|
# Supports OIDC_CLIENT_SECRET or OIDC_CLIENT_SECRET_FILE for Docker secrets.
|
||||||
|
# OIDC_CLIENT_SECRET is required only if OIDC is being used (indicated by explicit OIDC env vars).
|
||||||
|
oidc_base_url = System.get_env("OIDC_BASE_URL")
|
||||||
|
oidc_client_id = System.get_env("OIDC_CLIENT_ID")
|
||||||
|
oidc_in_use = not is_nil(oidc_base_url) or not is_nil(oidc_client_id)
|
||||||
|
|
||||||
|
client_secret =
|
||||||
|
if oidc_in_use do
|
||||||
|
get_env_or_file!.("OIDC_CLIENT_SECRET", """
|
||||||
|
environment variable OIDC_CLIENT_SECRET (or OIDC_CLIENT_SECRET_FILE) is missing.
|
||||||
|
This is required when OIDC authentication is configured (OIDC_BASE_URL or OIDC_CLIENT_ID is set).
|
||||||
|
""")
|
||||||
|
else
|
||||||
|
get_env_or_file.("OIDC_CLIENT_SECRET", nil)
|
||||||
|
end
|
||||||
|
|
||||||
config :mv, :rauthy,
|
config :mv, :rauthy,
|
||||||
client_id: System.get_env("OIDC_CLIENT_ID") || "mv",
|
client_id: oidc_client_id || "mv",
|
||||||
base_url: System.get_env("OIDC_BASE_URL") || "http://localhost:8080/auth/v1",
|
base_url: oidc_base_url || "http://localhost:8080/auth/v1",
|
||||||
client_secret: System.get_env("OIDC_CLIENT_SECRET"),
|
client_secret: client_secret,
|
||||||
redirect_uri:
|
redirect_uri:
|
||||||
System.get_env("OIDC_REDIRECT_URI") || "http://#{host}:#{port}/auth/user/rauthy/callback"
|
System.get_env("OIDC_REDIRECT_URI") || "http://#{host}:#{port}/auth/user/rauthy/callback"
|
||||||
|
|
||||||
# Token signing secret from environment variable
|
# Token signing secret from environment variable
|
||||||
# This overrides the placeholder value set in prod.exs
|
# This overrides the placeholder value set in prod.exs
|
||||||
|
# Supports TOKEN_SIGNING_SECRET or TOKEN_SIGNING_SECRET_FILE for Docker secrets.
|
||||||
token_signing_secret =
|
token_signing_secret =
|
||||||
System.get_env("TOKEN_SIGNING_SECRET") ||
|
get_env_or_file!.("TOKEN_SIGNING_SECRET", """
|
||||||
raise """
|
environment variable TOKEN_SIGNING_SECRET (or TOKEN_SIGNING_SECRET_FILE) is missing.
|
||||||
environment variable TOKEN_SIGNING_SECRET is missing.
|
You can generate one by calling: mix phx.gen.secret
|
||||||
You can generate one by calling: mix phx.gen.secret
|
""")
|
||||||
"""
|
|
||||||
|
|
||||||
config :mv, :token_signing_secret, token_signing_secret
|
config :mv, :token_signing_secret, token_signing_secret
|
||||||
|
|
||||||
config :mv, MvWeb.Endpoint,
|
config :mv, MvWeb.Endpoint,
|
||||||
url: [host: host, port: 443, scheme: "https"],
|
url: [host: host, port: 443, scheme: "https"],
|
||||||
http: [
|
http: [
|
||||||
# Enable IPv6 and bind on all interfaces.
|
# Bind on all IPv4 interfaces.
|
||||||
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
# 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
|
# 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},
|
||||||
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
|
||||||
port: port
|
port: port
|
||||||
],
|
],
|
||||||
secret_key_base: secret_key_base,
|
secret_key_base: secret_key_base,
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,32 @@ services:
|
||||||
app:
|
app:
|
||||||
image: git.local-it.org/local-it/mitgliederverwaltung:latest
|
image: git.local-it.org/local-it/mitgliederverwaltung:latest
|
||||||
container_name: mv-prod-app
|
container_name: mv-prod-app
|
||||||
# Use host network for local testing to access localhost:8080 (Rauthy)
|
ports:
|
||||||
# In real production, remove this and use external OIDC provider
|
- "4001:4001"
|
||||||
network_mode: host
|
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: "ecto://postgres:postgres@localhost:5001/mv_prod"
|
# Database configuration using separate variables
|
||||||
SECRET_KEY_BASE: "${SECRET_KEY_BASE}"
|
# Use Docker service name for internal networking
|
||||||
TOKEN_SIGNING_SECRET: "${TOKEN_SIGNING_SECRET}"
|
DATABASE_HOST: "db-prod"
|
||||||
PHX_HOST: "${PHX_HOST}"
|
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"
|
PORT: "4001"
|
||||||
PHX_SERVER: "true"
|
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_CLIENT_ID: "mv"
|
||||||
OIDC_BASE_URL: "http://localhost:8080/auth/v1"
|
OIDC_BASE_URL: "http://host.docker.internal:8080/auth/v1"
|
||||||
OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET:-}"
|
OIDC_CLIENT_SECRET_FILE: "/run/secrets/oidc_client_secret"
|
||||||
OIDC_REDIRECT_URI: "http://localhost:4001/auth/user/rauthy/callback"
|
OIDC_REDIRECT_URI: "http://localhost:4001/auth/user/rauthy/callback"
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
|
- secret_key_base
|
||||||
|
- token_signing_secret
|
||||||
|
- oidc_client_secret
|
||||||
depends_on:
|
depends_on:
|
||||||
- db-prod
|
- db-prod
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
@ -26,13 +37,25 @@ services:
|
||||||
container_name: mv-prod-db
|
container_name: mv-prod-db
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
|
||||||
POSTGRES_DB: mv_prod
|
POSTGRES_DB: mv_prod
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data_prod:/var/lib/postgresql/data
|
- postgres_data_prod:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
- "5001:5432"
|
- "5001:5432"
|
||||||
restart: unless-stopped
|
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:
|
volumes:
|
||||||
postgres_data_prod:
|
postgres_data_prod:
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
<p>{msg}</p>
|
<p>{msg}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<button type="button" class="group self-start cursor-pointer" aria-label={gettext("close")}>
|
<button type="button" class="self-start cursor-pointer group" aria-label={gettext("close")}>
|
||||||
<.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
|
<.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,61 +368,63 @@ defmodule MvWeb.CoreComponents do
|
||||||
end
|
end
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<table class="table table-zebra">
|
<div class="overflow-auto">
|
||||||
<thead>
|
<table class="table table-zebra">
|
||||||
<tr>
|
<thead>
|
||||||
<th :for={col <- @col}>{col[:label]}</th>
|
<tr>
|
||||||
<th :for={dyn_col <- @dynamic_cols}>
|
<th :for={col <- @col}>{col[:label]}</th>
|
||||||
<.live_component
|
<th :for={dyn_col <- @dynamic_cols}>
|
||||||
module={MvWeb.Components.SortHeaderComponent}
|
<.live_component
|
||||||
id={:"sort_custom_field_#{dyn_col[:custom_field].id}"}
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
field={"custom_field_#{dyn_col[:custom_field].id}"}
|
id={:"sort_custom_field_#{dyn_col[:custom_field].id}"}
|
||||||
label={dyn_col[:custom_field].name}
|
field={"custom_field_#{dyn_col[:custom_field].id}"}
|
||||||
sort_field={@sort_field}
|
label={dyn_col[:custom_field].name}
|
||||||
sort_order={@sort_order}
|
sort_field={@sort_field}
|
||||||
/>
|
sort_order={@sort_order}
|
||||||
</th>
|
/>
|
||||||
<th :if={@action != []}>
|
</th>
|
||||||
<span class="sr-only">{gettext("Actions")}</span>
|
<th :if={@action != []}>
|
||||||
</th>
|
<span class="sr-only">{gettext("Actions")}</span>
|
||||||
</tr>
|
</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}>
|
</thead>
|
||||||
<tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
|
<tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}>
|
||||||
<td
|
<tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
|
||||||
:for={col <- @col}
|
<td
|
||||||
phx-click={@row_click && @row_click.(row)}
|
:for={col <- @col}
|
||||||
class={@row_click && "hover:cursor-pointer"}
|
phx-click={@row_click && @row_click.(row)}
|
||||||
>
|
class={["max-w-xs truncate", @row_click && "hover:cursor-pointer"]}
|
||||||
{render_slot(col, @row_item.(row))}
|
>
|
||||||
</td>
|
{render_slot(col, @row_item.(row))}
|
||||||
<td
|
</td>
|
||||||
:for={dyn_col <- @dynamic_cols}
|
<td
|
||||||
phx-click={@row_click && @row_click.(row)}
|
:for={dyn_col <- @dynamic_cols}
|
||||||
class={@row_click && "hover:cursor-pointer"}
|
phx-click={@row_click && @row_click.(row)}
|
||||||
>
|
class={["max-w-xs truncate", @row_click && "hover:cursor-pointer"]}
|
||||||
{if dyn_col[:render] do
|
>
|
||||||
rendered = dyn_col[:render].(@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
|
else
|
||||||
rendered
|
""
|
||||||
end
|
end}
|
||||||
else
|
</td>
|
||||||
""
|
<td :if={@action != []} class="w-0 font-semibold">
|
||||||
end}
|
<div class="flex gap-4">
|
||||||
</td>
|
<%= for action <- @action do %>
|
||||||
<td :if={@action != []} class="w-0 font-semibold">
|
{render_slot(action, @row_item.(row))}
|
||||||
<div class="flex gap-4">
|
<% end %>
|
||||||
<%= for action <- @action do %>
|
</div>
|
||||||
{render_slot(action, @row_item.(row))}
|
</td>
|
||||||
<% end %>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
</td>
|
</table>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ defmodule MvWeb.Layouts.Navbar do
|
||||||
<a class="btn btn-ghost text-xl">{@club_name}</a>
|
<a class="btn btn-ghost text-xl">{@club_name}</a>
|
||||||
<ul class="menu menu-horizontal bg-base-200">
|
<ul class="menu menu-horizontal bg-base-200">
|
||||||
<li><.link navigate="/members">{gettext("Members")}</.link></li>
|
<li><.link navigate="/members">{gettext("Members")}</.link></li>
|
||||||
<li><.link navigate="/custom_fields">{gettext("Custom Fields")}</.link></li>
|
<li><.link navigate="/settings">{gettext("Settings")}</.link></li>
|
||||||
<li><.link navigate="/users">{gettext("Users")}</.link></li>
|
<li><.link navigate="/users">{gettext("Users")}</.link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
27
lib/mv_web/helpers/date_formatter.ex
Normal file
27
lib/mv_web/helpers/date_formatter.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule MvWeb.Helpers.DateFormatter do
|
||||||
|
@moduledoc """
|
||||||
|
Centralized date formatting helper for the application.
|
||||||
|
Formats dates in European format (dd.mm.yyyy).
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Gettext, backend: MvWeb.Gettext
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Formats a Date struct to European format (dd.mm.yyyy).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> MvWeb.Helpers.DateFormatter.format_date(~D[2024-03-15])
|
||||||
|
"15.03.2024"
|
||||||
|
|
||||||
|
iex> MvWeb.Helpers.DateFormatter.format_date(nil)
|
||||||
|
""
|
||||||
|
"""
|
||||||
|
def format_date(%Date{} = date) do
|
||||||
|
Calendar.strftime(date, "%d.%m.%Y")
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_date(nil), do: ""
|
||||||
|
|
||||||
|
def format_date(_), do: "Invalid date"
|
||||||
|
end
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule MvWeb.Components.SortHeaderComponent do
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="tooltip" data-tip={aria_sort(@field, @sort_field, @sort_order)}>
|
<div class="tooltip tooltip-bottom" data-tip={aria_sort(@field, @sort_field, @sort_order)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={aria_sort(@field, @sort_field, @sort_order)}
|
aria-label={aria_sort(@field, @sort_field, @sort_order)}
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
defmodule MvWeb.CustomFieldLive.Form do
|
|
||||||
@moduledoc """
|
|
||||||
LiveView form for creating and editing custom fields (admin).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Create new custom field definitions
|
|
||||||
- Edit existing custom fields
|
|
||||||
- Select value type from supported types
|
|
||||||
- Set immutable and required flags
|
|
||||||
- Real-time validation
|
|
||||||
|
|
||||||
## Form Fields
|
|
||||||
**Required:**
|
|
||||||
- name - Unique identifier (e.g., "phone_mobile", "emergency_contact")
|
|
||||||
- value_type - Data type (:string, :integer, :boolean, :date, :email)
|
|
||||||
|
|
||||||
**Optional:**
|
|
||||||
- description - Human-readable explanation
|
|
||||||
- immutable - If true, values cannot be changed after creation (default: false)
|
|
||||||
- required - If true, all members must have this custom field (default: false)
|
|
||||||
- show_in_overview - If true, this custom field will be displayed in the member overview table (default: true)
|
|
||||||
|
|
||||||
## Value Type Selection
|
|
||||||
- `:string` - Text data (unlimited length)
|
|
||||||
- `:integer` - Numeric data
|
|
||||||
- `:boolean` - True/false flags
|
|
||||||
- `:date` - Date values
|
|
||||||
- `:email` - Validated email addresses
|
|
||||||
|
|
||||||
## Events
|
|
||||||
- `validate` - Real-time form validation
|
|
||||||
- `save` - Submit form (create or update custom field)
|
|
||||||
|
|
||||||
## Security
|
|
||||||
Custom field management is restricted to admin users.
|
|
||||||
"""
|
|
||||||
use MvWeb, :live_view
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
|
||||||
<.header>
|
|
||||||
{@page_title}
|
|
||||||
<:subtitle>
|
|
||||||
{gettext("Use this form to manage custom_field records in your database.")}
|
|
||||||
</:subtitle>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.form for={@form} id="custom_field-form" phx-change="validate" phx-submit="save">
|
|
||||||
<.input field={@form[:name]} type="text" label={gettext("Name")} />
|
|
||||||
|
|
||||||
<.input
|
|
||||||
field={@form[:value_type]}
|
|
||||||
type="select"
|
|
||||||
label={gettext("Value type")}
|
|
||||||
options={
|
|
||||||
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<.input field={@form[:description]} type="text" label={gettext("Description")} />
|
|
||||||
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
|
|
||||||
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
|
|
||||||
<.input field={@form[:show_in_overview]} type="checkbox" label={gettext("Show in overview")} />
|
|
||||||
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
|
||||||
{gettext("Save Custom field")}
|
|
||||||
</.button>
|
|
||||||
<.button navigate={return_path(@return_to, @custom_field)}>{gettext("Cancel")}</.button>
|
|
||||||
</.form>
|
|
||||||
</Layouts.app>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(params, _session, socket) do
|
|
||||||
custom_field =
|
|
||||||
case params["id"] do
|
|
||||||
nil -> nil
|
|
||||||
id -> Ash.get!(Mv.Membership.CustomField, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
action = if is_nil(custom_field), do: "New", else: "Edit"
|
|
||||||
page_title = action <> " " <> "Custom field"
|
|
||||||
|
|
||||||
{:ok,
|
|
||||||
socket
|
|
||||||
|> assign(:return_to, return_to(params["return_to"]))
|
|
||||||
|> assign(custom_field: custom_field)
|
|
||||||
|> assign(:page_title, page_title)
|
|
||||||
|> assign_form()}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp return_to("show"), do: "show"
|
|
||||||
defp return_to(_), do: "index"
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
|
|
||||||
{:noreply,
|
|
||||||
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
|
|
||||||
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
|
|
||||||
{:ok, custom_field} ->
|
|
||||||
notify_parent({:saved, custom_field})
|
|
||||||
|
|
||||||
action =
|
|
||||||
case socket.assigns.form.source.type do
|
|
||||||
:create -> gettext("create")
|
|
||||||
:update -> gettext("update")
|
|
||||||
other -> to_string(other)
|
|
||||||
end
|
|
||||||
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> put_flash(:info, gettext("Custom field %{action} successfully", action: action))
|
|
||||||
|> push_navigate(to: return_path(socket.assigns.return_to, custom_field))
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
|
|
||||||
{:error, form} ->
|
|
||||||
{:noreply, assign(socket, form: form)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
|
||||||
|
|
||||||
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
|
|
||||||
form =
|
|
||||||
if custom_field do
|
|
||||||
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
|
|
||||||
else
|
|
||||||
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
|
|
||||||
end
|
|
||||||
|
|
||||||
assign(socket, form: to_form(form))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp return_path("index", _custom_field), do: ~p"/custom_fields"
|
|
||||||
defp return_path("show", custom_field), do: ~p"/custom_fields/#{custom_field.id}"
|
|
||||||
end
|
|
||||||
122
lib/mv_web/live/custom_field_live/form_component.ex
Normal file
122
lib/mv_web/live/custom_field_live/form_component.ex
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
defmodule MvWeb.CustomFieldLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
LiveComponent form for creating and editing custom fields (embedded in settings).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Create new custom field definitions
|
||||||
|
- Edit existing custom fields
|
||||||
|
- Select value type from supported types
|
||||||
|
- Set immutable and required flags
|
||||||
|
- Real-time validation
|
||||||
|
|
||||||
|
## Props
|
||||||
|
- `custom_field` - The custom field to edit (nil for new)
|
||||||
|
- `on_save` - Callback function to call when form is saved
|
||||||
|
- `on_cancel` - Callback function to call when form is cancelled
|
||||||
|
"""
|
||||||
|
use MvWeb, :live_component
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id={@id} class="card bg-base-200 shadow-xl mb-8">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<.button type="button" phx-click="cancel" phx-target={@myself}>
|
||||||
|
<.icon name="hero-arrow-left" class="w-4 h-4" />
|
||||||
|
</.button>
|
||||||
|
<h3 class="card-title">
|
||||||
|
{if @custom_field, do: gettext("Edit Custom Field"), else: gettext("New Custom Field")}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<.form
|
||||||
|
for={@form}
|
||||||
|
id={@id <> "-form"}
|
||||||
|
phx-change="validate"
|
||||||
|
phx-submit="save"
|
||||||
|
phx-target={@myself}
|
||||||
|
>
|
||||||
|
<.input field={@form[:name]} type="text" label={gettext("Name")} />
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={@form[:value_type]}
|
||||||
|
type="select"
|
||||||
|
label={gettext("Value type")}
|
||||||
|
options={
|
||||||
|
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<.input field={@form[:description]} type="text" label={gettext("Description")} />
|
||||||
|
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
|
||||||
|
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
|
||||||
|
<.input
|
||||||
|
field={@form[:show_in_overview]}
|
||||||
|
type="checkbox"
|
||||||
|
label={gettext("Show in overview")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="card-actions justify-end mt-4">
|
||||||
|
<.button type="button" phx-click="cancel" phx-target={@myself}>
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</.button>
|
||||||
|
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||||
|
{gettext("Save Custom field")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
</.form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(assigns, socket) do
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign_form()}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
|
||||||
|
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
|
||||||
|
{:ok, custom_field} ->
|
||||||
|
action =
|
||||||
|
case socket.assigns.form.source.type do
|
||||||
|
:create -> gettext("create")
|
||||||
|
:update -> gettext("update")
|
||||||
|
other -> to_string(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
socket.assigns.on_save.(custom_field, action)
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
|
{:error, form} ->
|
||||||
|
{:noreply, assign(socket, form: form)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("cancel", _params, socket) do
|
||||||
|
socket.assigns.on_cancel.()
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
|
||||||
|
form =
|
||||||
|
if custom_field do
|
||||||
|
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
|
||||||
|
else
|
||||||
|
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
|
||||||
|
end
|
||||||
|
|
||||||
|
assign(socket, form: to_form(form))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
defmodule MvWeb.CustomFieldLive.Index do
|
|
||||||
@moduledoc """
|
|
||||||
LiveView for managing custom field definitions (admin).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- List all custom fields
|
|
||||||
- Display type information (name, value type, description)
|
|
||||||
- Show immutable and required flags
|
|
||||||
- Create new custom fields
|
|
||||||
- Edit existing custom fields
|
|
||||||
- Delete custom fields with confirmation (cascades to all custom field values)
|
|
||||||
|
|
||||||
## Displayed Information
|
|
||||||
- Name: Unique identifier for the custom field
|
|
||||||
- Value type: Data type constraint (string, integer, boolean, date, email)
|
|
||||||
- Description: Human-readable explanation
|
|
||||||
- Immutable: Whether custom field values can be changed after creation
|
|
||||||
- Required: Whether all members must have this custom field (future feature)
|
|
||||||
|
|
||||||
## Events
|
|
||||||
- `prepare_delete` - Opens deletion confirmation modal with member count
|
|
||||||
- `confirm_delete` - Executes deletion after slug verification
|
|
||||||
- `cancel_delete` - Cancels deletion and closes modal
|
|
||||||
- `update_slug_confirmation` - Updates slug input state
|
|
||||||
|
|
||||||
## Security
|
|
||||||
Custom field management is restricted to admin users.
|
|
||||||
Deletion requires entering the custom field's slug to prevent accidental deletions.
|
|
||||||
"""
|
|
||||||
use MvWeb, :live_view
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
|
||||||
<.header>
|
|
||||||
Listing Custom fields
|
|
||||||
<:actions>
|
|
||||||
<.button variant="primary" navigate={~p"/custom_fields/new"}>
|
|
||||||
<.icon name="hero-plus" /> New Custom field
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.table
|
|
||||||
id="custom_fields"
|
|
||||||
rows={@streams.custom_fields}
|
|
||||||
row_click={fn {_id, custom_field} -> JS.navigate(~p"/custom_fields/#{custom_field}") end}
|
|
||||||
>
|
|
||||||
<:col :let={{_id, custom_field}} label="Name">{custom_field.name}</:col>
|
|
||||||
|
|
||||||
<:col :let={{_id, custom_field}} label="Description">{custom_field.description}</:col>
|
|
||||||
|
|
||||||
<:action :let={{_id, custom_field}}>
|
|
||||||
<div class="sr-only">
|
|
||||||
<.link navigate={~p"/custom_fields/#{custom_field}"}>Show</.link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<.link navigate={~p"/custom_fields/#{custom_field}/edit"}>Edit</.link>
|
|
||||||
</:action>
|
|
||||||
|
|
||||||
<:action :let={{_id, custom_field}}>
|
|
||||||
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id})}>
|
|
||||||
Delete
|
|
||||||
</.link>
|
|
||||||
</:action>
|
|
||||||
</.table>
|
|
||||||
|
|
||||||
<%!-- Delete Confirmation Modal --%>
|
|
||||||
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
|
|
||||||
<div class="modal-box">
|
|
||||||
<h3 class="font-bold text-lg">{gettext("Delete Custom Field")}</h3>
|
|
||||||
|
|
||||||
<div class="py-4 space-y-4">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">
|
|
||||||
{ngettext(
|
|
||||||
"%{count} member has a value assigned for this custom field.",
|
|
||||||
"%{count} members have values assigned for this custom field.",
|
|
||||||
@custom_field_to_delete.assigned_members_count,
|
|
||||||
count: @custom_field_to_delete.assigned_members_count
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p class="text-sm mt-2">
|
|
||||||
{gettext(
|
|
||||||
"All custom field values will be permanently deleted when you delete this custom field."
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="slug-confirmation" class="label">
|
|
||||||
<span class="label-text">
|
|
||||||
{gettext("To confirm deletion, please enter this text:")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="font-mono font-bold text-lg mb-2 p-2 bg-base-200 rounded break-all">
|
|
||||||
{@custom_field_to_delete.slug}
|
|
||||||
</div>
|
|
||||||
<form phx-change="update_slug_confirmation">
|
|
||||||
<input
|
|
||||||
id="slug-confirmation"
|
|
||||||
name="slug"
|
|
||||||
type="text"
|
|
||||||
value={@slug_confirmation}
|
|
||||||
placeholder={gettext("Enter the text above to confirm")}
|
|
||||||
autocomplete="off"
|
|
||||||
phx-mounted={JS.focus()}
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<button phx-click="cancel_delete" class="btn">
|
|
||||||
{gettext("Cancel")}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
phx-click="confirm_delete"
|
|
||||||
class="btn btn-error"
|
|
||||||
disabled={@slug_confirmation != @custom_field_to_delete.slug}
|
|
||||||
>
|
|
||||||
{gettext("Delete Custom Field and All Values")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
</Layouts.app>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
{:ok,
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, "Listing Custom fields")
|
|
||||||
|> assign(:show_delete_modal, false)
|
|
||||||
|> assign(:custom_field_to_delete, nil)
|
|
||||||
|> assign(:slug_confirmation, "")
|
|
||||||
|> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField))}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("prepare_delete", %{"id" => id}, socket) do
|
|
||||||
custom_field = Ash.get!(Mv.Membership.CustomField, id, load: [:assigned_members_count])
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> assign(:custom_field_to_delete, custom_field)
|
|
||||||
|> assign(:show_delete_modal, true)
|
|
||||||
|> assign(:slug_confirmation, "")}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("update_slug_confirmation", %{"slug" => slug}, socket) do
|
|
||||||
{:noreply, assign(socket, :slug_confirmation, slug)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("confirm_delete", _params, socket) do
|
|
||||||
custom_field = socket.assigns.custom_field_to_delete
|
|
||||||
|
|
||||||
if socket.assigns.slug_confirmation == custom_field.slug do
|
|
||||||
# Delete the custom field (CASCADE will handle custom field values)
|
|
||||||
case Ash.destroy(custom_field) do
|
|
||||||
:ok ->
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:info, "Custom field deleted successfully")
|
|
||||||
|> assign(:show_delete_modal, false)
|
|
||||||
|> assign(:custom_field_to_delete, nil)
|
|
||||||
|> assign(:slug_confirmation, "")
|
|
||||||
|> stream_delete(:custom_fields, custom_field)}
|
|
||||||
|
|
||||||
{:error, error} ->
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:error, "Failed to delete custom field: #{inspect(error)}")}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:error, "Slug does not match. Deletion cancelled.")}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("cancel_delete", _params, socket) do
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> assign(:show_delete_modal, false)
|
|
||||||
|> assign(:custom_field_to_delete, nil)
|
|
||||||
|> assign(:slug_confirmation, "")}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
259
lib/mv_web/live/custom_field_live/index_component.ex
Normal file
259
lib/mv_web/live/custom_field_live/index_component.ex
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
|
@moduledoc """
|
||||||
|
LiveComponent for managing custom field definitions (embedded in settings).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- List all custom fields
|
||||||
|
- Display type information (name, value type, description)
|
||||||
|
- Show immutable and required flags
|
||||||
|
- Create new custom fields
|
||||||
|
- Edit existing custom fields
|
||||||
|
- Delete custom fields with confirmation (cascades to all custom field values)
|
||||||
|
"""
|
||||||
|
use MvWeb, :live_component
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id={@id}>
|
||||||
|
<.header>
|
||||||
|
{gettext("Custom Fields")}
|
||||||
|
<:subtitle>
|
||||||
|
{gettext("These will appear in addition to other data when adding new members.")}
|
||||||
|
</:subtitle>
|
||||||
|
<:actions>
|
||||||
|
<.button variant="primary" phx-click="new_custom_field" phx-target={@myself}>
|
||||||
|
<.icon name="hero-plus" /> {gettext("New Custom field")}
|
||||||
|
</.button>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<%!-- Show form when creating or editing --%>
|
||||||
|
<div :if={@show_form} class="mb-8">
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.CustomFieldLive.FormComponent}
|
||||||
|
id={@form_id}
|
||||||
|
custom_field={@editing_custom_field}
|
||||||
|
on_save={fn custom_field, action -> send(self(), {:custom_field_saved, custom_field, action}) end}
|
||||||
|
on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%!-- Hide table when form is visible --%>
|
||||||
|
<.table
|
||||||
|
:if={!@show_form}
|
||||||
|
id="custom_fields"
|
||||||
|
rows={@streams.custom_fields}
|
||||||
|
row_click={
|
||||||
|
fn {_id, custom_field} ->
|
||||||
|
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>
|
||||||
|
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Value Type")}>
|
||||||
|
{custom_field.value_type}
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Description")}>
|
||||||
|
{custom_field.description}
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:col :let={{_id, custom_field}} label={gettext("Show in Overview")}>
|
||||||
|
<span :if={custom_field.show_in_overview} class="badge badge-success">
|
||||||
|
{gettext("Yes")}
|
||||||
|
</span>
|
||||||
|
<span :if={!custom_field.show_in_overview} class="badge badge-ghost">
|
||||||
|
{gettext("No")}
|
||||||
|
</span>
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:action :let={{_id, custom_field}}>
|
||||||
|
<.link phx-click={
|
||||||
|
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
|
||||||
|
}>
|
||||||
|
{gettext("Edit")}
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
|
||||||
|
<:action :let={{_id, custom_field}}>
|
||||||
|
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}>
|
||||||
|
{gettext("Delete")}
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
|
||||||
|
<%!-- Delete Confirmation Modal --%>
|
||||||
|
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="text-lg font-bold">{gettext("Delete Custom Field")}</h3>
|
||||||
|
|
||||||
|
<div class="py-4 space-y-4">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">
|
||||||
|
{ngettext(
|
||||||
|
"%{count} member has a value assigned for this custom field.",
|
||||||
|
"%{count} members have values assigned for this custom field.",
|
||||||
|
@custom_field_to_delete.assigned_members_count,
|
||||||
|
count: @custom_field_to_delete.assigned_members_count
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-sm">
|
||||||
|
{gettext(
|
||||||
|
"All custom field values will be permanently deleted when you delete this custom field."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="slug-confirmation" class="label">
|
||||||
|
<span class="label-text">
|
||||||
|
{gettext("To confirm deletion, please enter this text:")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="p-2 mb-2 font-mono text-lg font-bold break-all rounded bg-base-200">
|
||||||
|
{@custom_field_to_delete.slug}
|
||||||
|
</div>
|
||||||
|
<form phx-change="update_slug_confirmation" phx-target={@myself}>
|
||||||
|
<input
|
||||||
|
id="slug-confirmation"
|
||||||
|
name="slug"
|
||||||
|
type="text"
|
||||||
|
value={@slug_confirmation}
|
||||||
|
placeholder={gettext("Enter the text above to confirm")}
|
||||||
|
autocomplete="off"
|
||||||
|
phx-mounted={JS.focus()}
|
||||||
|
class="w-full input input-bordered"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<button phx-click="cancel_delete" phx-target={@myself} class="btn">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
phx-click="confirm_delete"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-error"
|
||||||
|
disabled={@slug_confirmation != @custom_field_to_delete.slug}
|
||||||
|
>
|
||||||
|
{gettext("Delete Custom Field and All Values")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(assigns, socket) do
|
||||||
|
# If show_form is explicitly provided in assigns, reset editing state
|
||||||
|
socket =
|
||||||
|
if Map.has_key?(assigns, :show_form) and assigns.show_form == false do
|
||||||
|
socket
|
||||||
|
|> assign(:editing_custom_field, nil)
|
||||||
|
|> assign(:form_id, "custom-field-form-new")
|
||||||
|
else
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign_new(:show_form, fn -> false end)
|
||||||
|
|> assign_new(:form_id, fn -> "custom-field-form-new" end)
|
||||||
|
|> assign_new(:editing_custom_field, fn -> nil end)
|
||||||
|
|> assign_new(:show_delete_modal, fn -> false end)
|
||||||
|
|> assign_new(:custom_field_to_delete, fn -> nil end)
|
||||||
|
|> assign_new(:slug_confirmation, fn -> "" end)
|
||||||
|
|> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField), reset: true)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("new_custom_field", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_form, true)
|
||||||
|
|> assign(:editing_custom_field, nil)
|
||||||
|
|> assign(:form_id, "custom-field-form-new")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("edit_custom_field", %{"id" => id}, socket) do
|
||||||
|
custom_field = Ash.get!(Mv.Membership.CustomField, id)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_form, true)
|
||||||
|
|> assign(:editing_custom_field, custom_field)
|
||||||
|
|> assign(:form_id, "custom-field-form-#{id}")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("prepare_delete", %{"id" => id}, socket) do
|
||||||
|
custom_field = Ash.get!(Mv.Membership.CustomField, id, load: [:assigned_members_count])
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:custom_field_to_delete, custom_field)
|
||||||
|
|> assign(:show_delete_modal, true)
|
||||||
|
|> assign(:slug_confirmation, "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("update_slug_confirmation", %{"slug" => slug}, socket) do
|
||||||
|
{:noreply, assign(socket, :slug_confirmation, slug)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("confirm_delete", _params, socket) do
|
||||||
|
custom_field = socket.assigns.custom_field_to_delete
|
||||||
|
|
||||||
|
if socket.assigns.slug_confirmation == custom_field.slug do
|
||||||
|
case Ash.destroy(custom_field) do
|
||||||
|
:ok ->
|
||||||
|
send(self(), {:custom_field_deleted, custom_field})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_delete_modal, false)
|
||||||
|
|> assign(:custom_field_to_delete, nil)
|
||||||
|
|> assign(:slug_confirmation, "")
|
||||||
|
|> stream_delete(:custom_fields, custom_field)}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
send(self(), {:custom_field_delete_error, error})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_delete_modal, false)
|
||||||
|
|> assign(:custom_field_to_delete, nil)
|
||||||
|
|> assign(:slug_confirmation, "")}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
send(self(), :custom_field_slug_mismatch)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_delete_modal, false)
|
||||||
|
|> assign(:custom_field_to_delete, nil)
|
||||||
|
|> assign(:slug_confirmation, "")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("cancel_delete", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:show_delete_modal, false)
|
||||||
|
|> assign(:custom_field_to_delete, nil)
|
||||||
|
|> assign(:slug_confirmation, "")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
defmodule MvWeb.CustomFieldLive.Show do
|
|
||||||
@moduledoc """
|
|
||||||
LiveView for displaying a single custom field's details (admin).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Display custom field definition
|
|
||||||
- Show all attributes (name, value type, description, flags)
|
|
||||||
- Navigate to edit form
|
|
||||||
- Return to custom field list
|
|
||||||
|
|
||||||
## Displayed Information
|
|
||||||
- ID: Internal UUID identifier
|
|
||||||
- Slug: URL-friendly identifier (auto-generated, immutable)
|
|
||||||
- Name: Unique identifier
|
|
||||||
- Value type: Data type constraint
|
|
||||||
- Description: Optional explanation
|
|
||||||
- Immutable flag: Whether values can be changed
|
|
||||||
- Required flag: Whether all members need this custom field
|
|
||||||
|
|
||||||
## Navigation
|
|
||||||
- Back to custom field list
|
|
||||||
- Edit custom field
|
|
||||||
|
|
||||||
## Security
|
|
||||||
Custom field details are restricted to admin users.
|
|
||||||
"""
|
|
||||||
use MvWeb, :live_view
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
|
||||||
<.header>
|
|
||||||
Custom field {@custom_field.slug}
|
|
||||||
<:subtitle>This is a custom_field record from your database.</:subtitle>
|
|
||||||
|
|
||||||
<:actions>
|
|
||||||
<.button navigate={~p"/custom_fields"}>
|
|
||||||
<.icon name="hero-arrow-left" />
|
|
||||||
</.button>
|
|
||||||
<.button
|
|
||||||
variant="primary"
|
|
||||||
navigate={~p"/custom_fields/#{@custom_field}/edit?return_to=show"}
|
|
||||||
>
|
|
||||||
<.icon name="hero-pencil-square" /> Edit Custom field
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.list>
|
|
||||||
<:item title="Id">{@custom_field.id}</:item>
|
|
||||||
|
|
||||||
<:item title="Slug">
|
|
||||||
{@custom_field.slug}
|
|
||||||
<p class="mt-2 text-sm leading-6 text-zinc-600">
|
|
||||||
{gettext("Auto-generated identifier (immutable)")}
|
|
||||||
</p>
|
|
||||||
</:item>
|
|
||||||
|
|
||||||
<:item title="Name">{@custom_field.name}</:item>
|
|
||||||
|
|
||||||
<:item title="Description">{@custom_field.description}</:item>
|
|
||||||
</.list>
|
|
||||||
</Layouts.app>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(%{"id" => id}, _session, socket) do
|
|
||||||
{:ok,
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, "Show Custom field")
|
|
||||||
|> assign(:custom_field, Ash.get!(Mv.Membership.CustomField, id))}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -4,6 +4,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Edit the association/club name
|
- Edit the association/club name
|
||||||
|
- Manage custom fields
|
||||||
- Real-time form validation
|
- Real-time form validation
|
||||||
- Success/error feedback
|
- Success/error feedback
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, gettext("Club Settings"))
|
|> assign(:page_title, gettext("Settings"))
|
||||||
|> assign(:settings, settings)
|
|> assign(:settings, settings)
|
||||||
|> assign_form()}
|
|> assign_form()}
|
||||||
end
|
end
|
||||||
|
|
@ -38,12 +39,16 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
~H"""
|
~H"""
|
||||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||||
<.header>
|
<.header>
|
||||||
{gettext("Club Settings")}
|
{gettext("Settings")}
|
||||||
<:subtitle>
|
<:subtitle>
|
||||||
{gettext("Manage global settings for the association.")}
|
{gettext("Manage global settings for the association.")}
|
||||||
</:subtitle>
|
</:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
<%!-- Club Settings Section --%>
|
||||||
|
<.header>
|
||||||
|
{gettext("Club Settings")}
|
||||||
|
</.header>
|
||||||
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
||||||
<.input
|
<.input
|
||||||
field={@form[:club_name]}
|
field={@form[:club_name]}
|
||||||
|
|
@ -56,6 +61,12 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
{gettext("Save Settings")}
|
{gettext("Save Settings")}
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
|
|
||||||
|
<%!-- Custom Fields Section --%>
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.CustomFieldLive.IndexComponent}
|
||||||
|
id="custom-fields-component"
|
||||||
|
/>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
@ -66,6 +77,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, setting_params))}
|
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, setting_params))}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
def handle_event("save", %{"setting" => setting_params}, socket) do
|
def handle_event("save", %{"setting" => setting_params}, socket) do
|
||||||
case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do
|
case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do
|
||||||
{:ok, updated_settings} ->
|
{:ok, updated_settings} ->
|
||||||
|
|
@ -82,6 +94,37 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:custom_field_saved, _custom_field, action}, socket) do
|
||||||
|
send_update(MvWeb.CustomFieldLive.IndexComponent,
|
||||||
|
id: "custom-fields-component",
|
||||||
|
show_form: false
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
put_flash(socket, :info, gettext("Custom field %{action} successfully", action: action))}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:custom_field_deleted, _custom_field}, socket) do
|
||||||
|
{:noreply, put_flash(socket, :info, gettext("Custom field deleted successfully"))}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:custom_field_delete_error, error}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
put_flash(
|
||||||
|
socket,
|
||||||
|
:error,
|
||||||
|
gettext("Failed to delete custom field: %{error}", error: inspect(error))
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:custom_field_slug_mismatch, socket) do
|
||||||
|
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
|
||||||
|
end
|
||||||
|
|
||||||
defp assign_form(%{assigns: %{settings: settings}} = socket) do
|
defp assign_form(%{assigns: %{settings: settings}} = socket) do
|
||||||
form =
|
form =
|
||||||
AshPhoenix.Form.for_update(
|
AshPhoenix.Form.for_update(
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
|
|
||||||
alias Mv.Membership
|
alias Mv.Membership
|
||||||
alias MvWeb.MemberLive.Index.Formatter
|
alias MvWeb.MemberLive.Index.Formatter
|
||||||
|
alias MvWeb.Helpers.DateFormatter
|
||||||
|
|
||||||
# Prefix used in sort field names for custom fields (e.g., "custom_field_<id>")
|
# Prefix used in sort field names for custom fields (e.g., "custom_field_<id>")
|
||||||
@custom_field_prefix "custom_field_"
|
@custom_field_prefix "custom_field_"
|
||||||
|
|
@ -937,4 +938,7 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
Map.get(visibility_config, Atom.to_string(field), true)
|
Map.get(visibility_config, Atom.to_string(field), true)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Public helper function to format dates for use in templates
|
||||||
|
def format_date(date), do: DateFormatter.format_date(date)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{member.join_date}
|
{MvWeb.MemberLive.Index.format_date(member.join_date)}
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={member} label={gettext("Paid")}>
|
<:col :let={member} label={gettext("Paid")}>
|
||||||
<span class={[
|
<span class={[
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ defmodule MvWeb.MemberLive.Index.Formatter do
|
||||||
formats them appropriately for display in the UI.
|
formats them appropriately for display in the UI.
|
||||||
"""
|
"""
|
||||||
use Gettext, backend: MvWeb.Gettext
|
use Gettext, backend: MvWeb.Gettext
|
||||||
|
alias MvWeb.Helpers.DateFormatter
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Formats a custom field value for display.
|
Formats a custom field value for display.
|
||||||
|
|
@ -61,11 +62,11 @@ defmodule MvWeb.MemberLive.Index.Formatter do
|
||||||
defp format_value_by_type(value, :boolean, _) when value == false, do: gettext("No")
|
defp format_value_by_type(value, :boolean, _) when value == false, do: gettext("No")
|
||||||
defp format_value_by_type(value, :boolean, _), do: to_string(value)
|
defp format_value_by_type(value, :boolean, _), do: to_string(value)
|
||||||
|
|
||||||
defp format_value_by_type(%Date{} = date, :date, _), do: Date.to_string(date)
|
defp format_value_by_type(%Date{} = date, :date, _), do: DateFormatter.format_date(date)
|
||||||
|
|
||||||
defp format_value_by_type(value, :date, _) when is_binary(value) do
|
defp format_value_by_type(value, :date, _) when is_binary(value) do
|
||||||
case Date.from_iso8601(value) do
|
case Date.from_iso8601(value) do
|
||||||
{:ok, date} -> Date.to_string(date)
|
{:ok, date} -> DateFormatter.format_date(date)
|
||||||
_ -> value
|
_ -> value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
"""
|
"""
|
||||||
use MvWeb, :live_view
|
use MvWeb, :live_view
|
||||||
import Ash.Query
|
import Ash.Query
|
||||||
|
alias MvWeb.Helpers.DateFormatter
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
|
|
@ -52,8 +53,8 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
{if @member.paid, do: gettext("Yes"), else: gettext("No")}
|
{if @member.paid, do: gettext("Yes"), else: gettext("No")}
|
||||||
</:item>
|
</:item>
|
||||||
<:item title={gettext("Phone Number")}>{@member.phone_number}</:item>
|
<:item title={gettext("Phone Number")}>{@member.phone_number}</:item>
|
||||||
<:item title={gettext("Join Date")}>{@member.join_date}</:item>
|
<:item title={gettext("Join Date")}>{DateFormatter.format_date(@member.join_date)}</:item>
|
||||||
<:item title={gettext("Exit Date")}>{@member.exit_date}</:item>
|
<:item title={gettext("Exit Date")}>{DateFormatter.format_date(@member.exit_date)}</:item>
|
||||||
<:item title={gettext("Notes")}>{@member.notes}</:item>
|
<:item title={gettext("Notes")}>{@member.notes}</:item>
|
||||||
<:item title={gettext("City")}>{@member.city}</:item>
|
<:item title={gettext("City")}>{@member.city}</:item>
|
||||||
<:item title={gettext("Street")}>{@member.street}</:item>
|
<:item title={gettext("Street")}>{@member.street}</:item>
|
||||||
|
|
@ -81,10 +82,7 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
# name
|
# name
|
||||||
cfv.custom_field && cfv.custom_field.name,
|
cfv.custom_field && cfv.custom_field.name,
|
||||||
# value
|
# value
|
||||||
case cfv.value do
|
format_custom_field_value(cfv)
|
||||||
%{value: v} -> v
|
|
||||||
v -> v
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
} />
|
} />
|
||||||
|
|
@ -114,4 +112,17 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
|
|
||||||
defp page_title(:show), do: gettext("Show Member")
|
defp page_title(:show), do: gettext("Show Member")
|
||||||
defp page_title(:edit), do: gettext("Edit Member")
|
defp page_title(:edit), do: gettext("Edit Member")
|
||||||
|
|
||||||
|
defp format_custom_field_value(cfv) do
|
||||||
|
value =
|
||||||
|
case cfv.value do
|
||||||
|
%{value: v} -> v
|
||||||
|
v -> v
|
||||||
|
end
|
||||||
|
|
||||||
|
case value do
|
||||||
|
%Date{} = date -> DateFormatter.format_date(date)
|
||||||
|
other -> other
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
<:subtitle>{gettext("Use this form to manage user records in your database.")}</:subtitle>
|
<:subtitle>{gettext("Use this form to manage user records in your database.")}</:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.form for={@form} id="user-form" phx-change="validate" phx-submit="save">
|
<.form class="max-w-xl" for={@form} id="user-form" phx-change="validate" phx-submit="save">
|
||||||
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
|
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
|
||||||
|
|
||||||
<!-- Password Section -->
|
<!-- Password Section -->
|
||||||
|
|
@ -61,7 +61,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<%= if @show_password_fields do %>
|
<%= if @show_password_fields do %>
|
||||||
<div class="mt-4 space-y-4 p-4 bg-gray-50 rounded-lg">
|
<div class="p-4 mt-4 space-y-4 rounded-lg bg-gray-50">
|
||||||
<.input
|
<.input
|
||||||
field={@form[:password]}
|
field={@form[:password]}
|
||||||
label={gettext("Password")}
|
label={gettext("Password")}
|
||||||
|
|
@ -83,7 +83,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
|
|
||||||
<div class="text-sm text-gray-600">
|
<div class="text-sm text-gray-600">
|
||||||
<p><strong>{gettext("Password requirements")}:</strong></p>
|
<p><strong>{gettext("Password requirements")}:</strong></p>
|
||||||
<ul class="list-disc list-inside text-xs mt-1 space-y-1">
|
<ul class="mt-1 space-y-1 text-xs list-disc list-inside">
|
||||||
<li>{gettext("At least 8 characters")}</li>
|
<li>{gettext("At least 8 characters")}</li>
|
||||||
<li>{gettext("Include both letters and numbers")}</li>
|
<li>{gettext("Include both letters and numbers")}</li>
|
||||||
<li>{gettext("Consider using special characters")}</li>
|
<li>{gettext("Consider using special characters")}</li>
|
||||||
|
|
@ -91,7 +91,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @user do %>
|
<%= if @user do %>
|
||||||
<div class="mt-3 p-3 bg-orange-50 border border-orange-200 rounded">
|
<div class="p-3 mt-3 border border-orange-200 rounded bg-orange-50">
|
||||||
<p class="text-sm text-orange-800">
|
<p class="text-sm text-orange-800">
|
||||||
<strong>{gettext("Admin Note")}:</strong> {gettext(
|
<strong>{gettext("Admin Note")}:</strong> {gettext(
|
||||||
"As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
"As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||||
|
|
@ -102,7 +102,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= if @user do %>
|
<%= if @user do %>
|
||||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg">
|
<div class="p-4 mt-4 rounded-lg bg-blue-50">
|
||||||
<p class="text-sm text-blue-800">
|
<p class="text-sm text-blue-800">
|
||||||
<strong>{gettext("Note")}:</strong> {gettext(
|
<strong>{gettext("Note")}:</strong> {gettext(
|
||||||
"Check 'Change Password' above to set a new password for this user."
|
"Check 'Change Password' above to set a new password for this user."
|
||||||
|
|
@ -110,7 +110,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-4 p-4 bg-yellow-50 rounded-lg">
|
<div class="p-4 mt-4 rounded-lg bg-yellow-50">
|
||||||
<p class="text-sm text-yellow-800">
|
<p class="text-sm text-yellow-800">
|
||||||
<strong>{gettext("Note")}:</strong> {gettext(
|
<strong>{gettext("Note")}:</strong> {gettext(
|
||||||
"User will be created without a password. Check 'Set Password' to add one."
|
"User will be created without a password. Check 'Set Password' to add one."
|
||||||
|
|
@ -123,11 +123,11 @@ defmodule MvWeb.UserLive.Form do
|
||||||
|
|
||||||
<!-- Member Linking Section -->
|
<!-- Member Linking Section -->
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<h2 class="text-base font-semibold mb-3">{gettext("Linked Member")}</h2>
|
<h2 class="mb-3 text-base font-semibold">{gettext("Linked Member")}</h2>
|
||||||
|
|
||||||
<%= if @user && @user.member && !@unlink_member do %>
|
<%= if @user && @user.member && !@unlink_member do %>
|
||||||
<!-- Show linked member with unlink button -->
|
<!-- Show linked member with unlink button -->
|
||||||
<div class="p-4 bg-green-50 border border-green-200 rounded-lg">
|
<div class="p-4 border border-green-200 rounded-lg bg-green-50">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-green-900">
|
<p class="font-medium text-green-900">
|
||||||
|
|
@ -147,7 +147,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= if @unlink_member do %>
|
<%= if @unlink_member do %>
|
||||||
<!-- Show unlink pending message -->
|
<!-- Show unlink pending message -->
|
||||||
<div class="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
<div class="p-4 border border-yellow-200 rounded-lg bg-yellow-50">
|
||||||
<p class="text-sm text-yellow-800">
|
<p class="text-sm text-yellow-800">
|
||||||
<strong>{gettext("Unlinking scheduled")}:</strong> {gettext(
|
<strong>{gettext("Unlinking scheduled")}:</strong> {gettext(
|
||||||
"Member will be unlinked when you save. Cannot select new member until saved."
|
"Member will be unlinked when you save. Cannot select new member until saved."
|
||||||
|
|
@ -219,7 +219,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @user && @user.email && @available_members != [] && Enum.all?(@available_members, &(&1.email == to_string(@user.email))) do %>
|
<%= if @user && @user.email && @available_members != [] && Enum.all?(@available_members, &(&1.email == to_string(@user.email))) do %>
|
||||||
<div class="p-3 bg-yellow-50 border border-yellow-200 rounded">
|
<div class="p-3 border border-yellow-200 rounded bg-yellow-50">
|
||||||
<p class="text-sm text-yellow-800">
|
<p class="text-sm text-yellow-800">
|
||||||
<strong>{gettext("Note")}:</strong> {gettext(
|
<strong>{gettext("Note")}:</strong> {gettext(
|
||||||
"A member with this email already exists. To link with a different member, please change one of the email addresses first."
|
"A member with this email already exists. To link with a different member, please change one of the email addresses first."
|
||||||
|
|
@ -231,12 +231,12 @@ defmodule MvWeb.UserLive.Form do
|
||||||
<%= if @selected_member_id && @selected_member_name do %>
|
<%= if @selected_member_id && @selected_member_name do %>
|
||||||
<div
|
<div
|
||||||
id="member-selected"
|
id="member-selected"
|
||||||
class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg"
|
class="p-3 mt-2 border border-blue-200 rounded-lg bg-blue-50"
|
||||||
>
|
>
|
||||||
<p class="text-sm text-blue-800">
|
<p class="text-sm text-blue-800">
|
||||||
<strong>{gettext("Selected")}:</strong> {@selected_member_name}
|
<strong>{gettext("Selected")}:</strong> {@selected_member_name}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-blue-600 mt-1">
|
<p class="mt-1 text-xs text-blue-600">
|
||||||
{gettext("Save to confirm linking.")}
|
{gettext("Save to confirm linking.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -245,10 +245,12 @@ defmodule MvWeb.UserLive.Form do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
<div class="mt-4">
|
||||||
{gettext("Save User")}
|
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||||
</.button>
|
{gettext("Save User")}
|
||||||
<.button navigate={return_path(@return_to, @user)}>{gettext("Cancel")}</.button>
|
</.button>
|
||||||
|
<.button navigate={return_path(@return_to, @user)}>{gettext("Cancel")}</.button>
|
||||||
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
>
|
>
|
||||||
{user.email}
|
{user.email}
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={user} label={gettext("OIDC ID")}>{user.oidc_id}</:col>
|
|
||||||
<:col :let={user} label={gettext("Linked Member")}>
|
<:col :let={user} label={gettext("Linked Member")}>
|
||||||
<%= if user.member do %>
|
<%= if user.member do %>
|
||||||
{user.member.first_name} {user.member.last_name}
|
{user.member.first_name} {user.member.last_name}
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,7 @@ defmodule MvWeb.UserLive.Show do
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.list>
|
<.list>
|
||||||
<:item title={gettext("ID")}>{@user.id}</:item>
|
|
||||||
<:item title={gettext("Email")}>{@user.email}</:item>
|
<:item title={gettext("Email")}>{@user.email}</:item>
|
||||||
<:item title={gettext("OIDC ID")}>{@user.oidc_id || gettext("Not set")}</:item>
|
|
||||||
<:item title={gettext("Password Authentication")}>
|
<:item title={gettext("Password Authentication")}>
|
||||||
{if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")}
|
{if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")}
|
||||||
</:item>
|
</:item>
|
||||||
|
|
@ -56,13 +54,13 @@ defmodule MvWeb.UserLive.Show do
|
||||||
<%= if @user.member do %>
|
<%= if @user.member do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/members/#{@user.member}"}
|
navigate={~p"/members/#{@user.member}"}
|
||||||
class="text-blue-600 hover:text-blue-800 underline"
|
class="text-blue-600 underline hover:text-blue-800"
|
||||||
>
|
>
|
||||||
<.icon name="hero-users" class="h-4 w-4 inline mr-1" />
|
<.icon name="hero-users" class="inline w-4 h-4 mr-1" />
|
||||||
{@user.member.first_name} {@user.member.last_name}
|
{@user.member.first_name} {@user.member.last_name}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-500 italic">{gettext("No member linked")}</span>
|
<span class="italic text-gray-500">{gettext("No member linked")}</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:item>
|
</:item>
|
||||||
</.list>
|
</.list>
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,6 @@ defmodule MvWeb.Router do
|
||||||
live "/members/:id", MemberLive.Show, :show
|
live "/members/:id", MemberLive.Show, :show
|
||||||
live "/members/:id/show/edit", MemberLive.Show, :edit
|
live "/members/:id/show/edit", MemberLive.Show, :edit
|
||||||
|
|
||||||
live "/custom_fields", CustomFieldLive.Index, :index
|
|
||||||
live "/custom_fields/new", CustomFieldLive.Form, :new
|
|
||||||
live "/custom_fields/:id/edit", CustomFieldLive.Form, :edit
|
|
||||||
live "/custom_fields/:id", CustomFieldLive.Show, :show
|
|
||||||
live "/custom_fields/:id/show/edit", CustomFieldLive.Show, :edit
|
|
||||||
|
|
||||||
live "/custom_field_values", CustomFieldValueLive.Index, :index
|
live "/custom_field_values", CustomFieldValueLive.Index, :index
|
||||||
live "/custom_field_values/new", CustomFieldValueLive.Form, :new
|
live "/custom_field_values/new", CustomFieldValueLive.Form, :new
|
||||||
live "/custom_field_values/:id/edit", CustomFieldValueLive.Form, :edit
|
live "/custom_field_values/:id/edit", CustomFieldValueLive.Form, :edit
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,20 @@ msgstr "Verbindung wird wiederhergestellt"
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr "Stadt"
|
msgstr "Stadt"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:82
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:250
|
#: lib/mv_web/live/member_live/index.html.heex:250
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:74
|
#: lib/mv_web/live/user_live/index.html.heex:74
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Löschen"
|
msgstr "Löschen"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:76
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:242
|
#: 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/form.ex:265
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:66
|
#: lib/mv_web/live/user_live/index.html.heex:66
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Bearbeite"
|
msgstr "Bearbeiten"
|
||||||
|
|
||||||
#: lib/mv_web/live/member_live/show.ex:41
|
#: lib/mv_web/live/member_live/show.ex:41
|
||||||
#: lib/mv_web/live/member_live/show.ex:116
|
#: lib/mv_web/live/member_live/show.ex:116
|
||||||
|
|
@ -155,9 +157,9 @@ msgstr "Postleitzahl"
|
||||||
msgid "Save Member"
|
msgid "Save Member"
|
||||||
msgstr "Mitglied speichern"
|
msgstr "Mitglied speichern"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:66
|
#: lib/mv_web/live/custom_field_live/form_component.ex:63
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
||||||
#: lib/mv_web/live/global_settings_live.ex:55
|
#: lib/mv_web/live/global_settings_live.ex:60
|
||||||
#: lib/mv_web/live/member_live/form.ex:78
|
#: lib/mv_web/live/member_live/form.ex:78
|
||||||
#: lib/mv_web/live/user_live/form.ex:248
|
#: lib/mv_web/live/user_live/form.ex:248
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -176,6 +178,7 @@ msgstr "Straße"
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr "ID"
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:68
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:61
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -193,6 +196,7 @@ msgstr "Mitglied anzeigen"
|
||||||
msgid "This is a member record from your database."
|
msgid "This is a member record from your database."
|
||||||
msgstr "Dies ist ein Mitglied aus deiner Datenbank."
|
msgstr "Dies ist ein Mitglied aus deiner Datenbank."
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:65
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:60
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -200,14 +204,14 @@ msgstr "Dies ist ein Mitglied aus deiner Datenbank."
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr "Ja"
|
msgstr "Ja"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:110
|
#: lib/mv_web/live/custom_field_live/form_component.ex:93
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
||||||
#: lib/mv_web/live/member_live/form.ex:137
|
#: lib/mv_web/live/member_live/form.ex:137
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr "erstellt"
|
msgstr "erstellt"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:111
|
#: lib/mv_web/live/custom_field_live/form_component.ex:94
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
||||||
#: lib/mv_web/live/member_live/form.ex:138
|
#: lib/mv_web/live/member_live/form.ex:138
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -249,8 +253,8 @@ msgstr "Ihre E-Mail-Adresse wurde bestätigt"
|
||||||
msgid "Your password has successfully been reset"
|
msgid "Your password has successfully been reset"
|
||||||
msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
|
msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:69
|
#: lib/mv_web/live/custom_field_live/form_component.ex:61
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:120
|
#: lib/mv_web/live/custom_field_live/index_component.ex:138
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
||||||
#: lib/mv_web/live/member_live/form.ex:81
|
#: lib/mv_web/live/member_live/form.ex:81
|
||||||
#: lib/mv_web/live/user_live/form.ex:251
|
#: lib/mv_web/live/user_live/form.ex:251
|
||||||
|
|
@ -263,7 +267,8 @@ msgstr "Abbrechen"
|
||||||
msgid "Choose a member"
|
msgid "Choose a member"
|
||||||
msgstr "Mitglied auswählen"
|
msgstr "Mitglied auswählen"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:61
|
#: lib/mv_web/live/custom_field_live/form_component.ex:50
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:59
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Beschreibung"
|
msgstr "Beschreibung"
|
||||||
|
|
@ -283,7 +288,7 @@ msgstr "Aktiviert"
|
||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr "ID"
|
msgstr "ID"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:62
|
#: lib/mv_web/live/custom_field_live/form_component.ex:51
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr "Unveränderlich"
|
msgstr "Unveränderlich"
|
||||||
|
|
@ -311,7 +316,8 @@ msgstr "Mitglied"
|
||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr "Mitglieder"
|
msgstr "Mitglieder"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:51
|
#: lib/mv_web/live/custom_field_live/form_component.ex:40
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:53
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
@ -354,7 +360,7 @@ msgstr "Passwort-Authentifizierung"
|
||||||
msgid "Profil"
|
msgid "Profil"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:63
|
#: lib/mv_web/live/custom_field_live/form_component.ex:52
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr "Erforderlich"
|
msgstr "Erforderlich"
|
||||||
|
|
@ -369,7 +375,10 @@ msgstr "Alle Mitglieder auswählen"
|
||||||
msgid "Select member"
|
msgid "Select member"
|
||||||
msgstr "Mitglied auswählen"
|
msgstr "Mitglied auswählen"
|
||||||
|
|
||||||
|
#: lib/mv_web/components/layouts/navbar.ex:26
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:99
|
#: lib/mv_web/components/layouts/navbar.ex:99
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:32
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:42
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Einstellungen"
|
msgstr "Einstellungen"
|
||||||
|
|
@ -410,7 +419,7 @@ msgstr "Benutzer*in"
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Wert"
|
msgstr "Wert"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:56
|
#: lib/mv_web/live/custom_field_live/form_component.ex:45
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr "Wertetyp"
|
msgstr "Wertetyp"
|
||||||
|
|
@ -618,7 +627,7 @@ msgstr "Benutzerdefinierte Feldwerte"
|
||||||
msgid "Custom field"
|
msgid "Custom field"
|
||||||
msgstr "Benutzerdefiniertes Feld"
|
msgstr "Benutzerdefiniertes Feld"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:117
|
#: lib/mv_web/live/global_settings_live.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Custom field %{action} successfully"
|
msgid "Custom field %{action} successfully"
|
||||||
msgstr "Benutzerdefiniertes Feld erfolgreich %{action}"
|
msgstr "Benutzerdefiniertes Feld erfolgreich %{action}"
|
||||||
|
|
@ -633,7 +642,7 @@ msgstr "Benutzerdefinierter Feldwert erfolgreich %{action}"
|
||||||
msgid "Please select a custom field first"
|
msgid "Please select a custom field first"
|
||||||
msgstr "Bitte wähle zuerst ein Benutzerdefiniertes Feld"
|
msgstr "Bitte wähle zuerst ein Benutzerdefiniertes Feld"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:67
|
#: lib/mv_web/live/custom_field_live/form_component.ex:64
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Save Custom field"
|
msgid "Save Custom field"
|
||||||
msgstr "Benutzerdefiniertes Feld speichern"
|
msgstr "Benutzerdefiniertes Feld speichern"
|
||||||
|
|
@ -643,12 +652,7 @@ msgstr "Benutzerdefiniertes Feld speichern"
|
||||||
msgid "Save Custom field value"
|
msgid "Save Custom field value"
|
||||||
msgstr "Benutzerdefinierten Feldwert speichern"
|
msgstr "Benutzerdefinierten Feldwert speichern"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:46
|
#: lib/mv_web/live/custom_field_live/index_component.ex:20
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Use this form to manage custom_field records in your database."
|
|
||||||
msgstr "Verwende dieses Formular, um Benutzerdefinierte Felder in deiner Datenbank zu verwalten."
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:26
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Custom Fields"
|
msgid "Custom Fields"
|
||||||
msgstr "Benutzerdefinierte Felder"
|
msgstr "Benutzerdefinierte Felder"
|
||||||
|
|
@ -658,70 +662,64 @@ msgstr "Benutzerdefinierte Felder"
|
||||||
msgid "Use this form to manage Custom Field Value records in your database."
|
msgid "Use this form to manage Custom Field Value records in your database."
|
||||||
msgstr "Verwende dieses Formular, um Benutzerdefinierte Feldwerte in deiner Datenbank zu verwalten."
|
msgstr "Verwende dieses Formular, um Benutzerdefinierte Feldwerte in deiner Datenbank zu verwalten."
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/show.ex:56
|
#: lib/mv_web/live/custom_field_live/index_component.ex:97
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Auto-generated identifier (immutable)"
|
|
||||||
msgstr "Automatisch generierter Bezeichner (unveränderlich)"
|
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:79
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "%{count} member has a value assigned for this custom field."
|
msgid "%{count} member has a value assigned for this custom field."
|
||||||
msgid_plural "%{count} members have values assigned for this custom field."
|
msgid_plural "%{count} members have values assigned for this custom field."
|
||||||
msgstr[0] "%{count} Mitglied hat einen Wert für dieses benutzerdefinierte Feld zugewiesen."
|
msgstr[0] "%{count} Mitglied hat einen Wert für dieses benutzerdefinierte Feld zugewiesen."
|
||||||
msgstr[1] "%{count} Mitglieder haben Werte für dieses benutzerdefinierte Feld zugewiesen."
|
msgstr[1] "%{count} Mitglieder haben Werte für dieses benutzerdefinierte Feld zugewiesen."
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:87
|
#: lib/mv_web/live/custom_field_live/index_component.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
||||||
msgstr "Alle benutzerdefinierten Feldwerte werden beim Löschen dieses benutzerdefinierten Feldes dauerhaft gelöscht."
|
msgstr "Alle benutzerdefinierten Feldwerte werden beim Löschen dieses benutzerdefinierten Feldes dauerhaft gelöscht."
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:72
|
#: lib/mv_web/live/custom_field_live/index_component.ex:90
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field"
|
msgid "Delete Custom Field"
|
||||||
msgstr "Benutzerdefiniertes Feld löschen"
|
msgstr "Benutzerdefiniertes Feld löschen"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:127
|
#: lib/mv_web/live/custom_field_live/index_component.ex:146
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field and All Values"
|
msgid "Delete Custom Field and All Values"
|
||||||
msgstr "Benutzerdefiniertes Feld und alle Werte löschen"
|
msgstr "Benutzerdefiniertes Feld und alle Werte löschen"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:109
|
#: lib/mv_web/live/custom_field_live/index_component.ex:127
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enter the text above to confirm"
|
msgid "Enter the text above to confirm"
|
||||||
msgstr "Obigen Text zur Bestätigung eingeben"
|
msgstr "Obigen Text zur Bestätigung eingeben"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:97
|
#: lib/mv_web/live/custom_field_live/index_component.ex:115
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "To confirm deletion, please enter this text:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:"
|
msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:64
|
#: lib/mv_web/live/custom_field_live/form_component.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr "In der Mitglieder-Übersicht anzeigen"
|
msgstr "In der Mitglieder-Übersicht anzeigen"
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:51
|
#: lib/mv_web/live/global_settings_live.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Association Name"
|
msgid "Association Name"
|
||||||
msgstr "Vereinsname"
|
msgstr "Vereinsname"
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:31
|
#: lib/mv_web/live/global_settings_live.ex:50
|
||||||
#: lib/mv_web/live/global_settings_live.ex:41
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Club Settings"
|
msgid "Club Settings"
|
||||||
msgstr "Vereinsdaten"
|
msgstr "Vereinsdaten"
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:43
|
#: lib/mv_web/live/global_settings_live.ex:44
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Manage global settings for the association."
|
msgid "Manage global settings for the association."
|
||||||
msgstr "Passe übergreifende Einstellungen für den Verein an."
|
msgstr "Passe übergreifende Einstellungen für den Verein an."
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:56
|
#: lib/mv_web/live/global_settings_live.ex:61
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Save Settings"
|
msgid "Save Settings"
|
||||||
msgstr "Einstellungen speichern"
|
msgstr "Einstellungen speichern"
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:75
|
#: lib/mv_web/live/global_settings_live.ex:87
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings updated successfully"
|
msgid "Settings updated successfully"
|
||||||
msgstr "Einstellungen erfolgreich gespeichert"
|
msgstr "Einstellungen erfolgreich gespeichert"
|
||||||
|
|
@ -853,6 +851,51 @@ msgstr "Nicht bezahlt"
|
||||||
msgid "Payment filter"
|
msgid "Payment filter"
|
||||||
msgstr "Zahlungsfilter"
|
msgstr "Zahlungsfilter"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:110
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Custom field deleted successfully"
|
||||||
|
msgstr "Benutzerdefiniertes Feld erfolgreich %{action}"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Edit Custom Field"
|
||||||
|
msgstr "Benutzerdefiniertes Feld bearbeiten"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:119
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to delete custom field: %{error}"
|
||||||
|
msgstr "Konnte benutzerdefiniertes Feld nicht löschen: %{error}"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "New Custom Field"
|
||||||
|
msgstr "Neues Benutzerdefiniertes Feld"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:26
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "New Custom field"
|
||||||
|
msgstr "Neues Benutzerdefiniertes Feld"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:63
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Show in Overview"
|
||||||
|
msgstr "In der Mitglieder-Übersicht anzeigen"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:125
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Slug does not match. Deletion cancelled."
|
||||||
|
msgstr "Eingegebener Text war nicht korrekt. Löschen wurde abgebrochen."
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:55
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Value Type"
|
||||||
|
msgstr "Wertetyp"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "These will appear in addition to other data when adding new members."
|
||||||
|
msgstr "Diese Felder können zusätzlich zu den normalen Daten ausgefüllt werden, wenn ein neues Mitglied angelegt wird."
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/member_live/form.ex:48
|
#~ #: lib/mv_web/live/member_live/form.ex:48
|
||||||
#~ #: lib/mv_web/live/member_live/show.ex:51
|
#~ #: lib/mv_web/live/member_live/show.ex:51
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,14 @@ msgstr ""
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:82
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:250
|
#: lib/mv_web/live/member_live/index.html.heex:250
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:74
|
#: lib/mv_web/live/user_live/index.html.heex:74
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:76
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:242
|
#: 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/form.ex:265
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:66
|
#: lib/mv_web/live/user_live/index.html.heex:66
|
||||||
|
|
@ -156,9 +158,9 @@ msgstr ""
|
||||||
msgid "Save Member"
|
msgid "Save Member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:66
|
#: lib/mv_web/live/custom_field_live/form_component.ex:63
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
||||||
#: lib/mv_web/live/global_settings_live.ex:55
|
#: lib/mv_web/live/global_settings_live.ex:60
|
||||||
#: lib/mv_web/live/member_live/form.ex:78
|
#: lib/mv_web/live/member_live/form.ex:78
|
||||||
#: lib/mv_web/live/user_live/form.ex:248
|
#: lib/mv_web/live/user_live/form.ex:248
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -177,6 +179,7 @@ msgstr ""
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:68
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:61
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -194,6 +197,7 @@ msgstr ""
|
||||||
msgid "This is a member record from your database."
|
msgid "This is a member record from your database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:65
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:60
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -201,14 +205,14 @@ msgstr ""
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:110
|
#: lib/mv_web/live/custom_field_live/form_component.ex:93
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
||||||
#: lib/mv_web/live/member_live/form.ex:137
|
#: lib/mv_web/live/member_live/form.ex:137
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:111
|
#: lib/mv_web/live/custom_field_live/form_component.ex:94
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
||||||
#: lib/mv_web/live/member_live/form.ex:138
|
#: lib/mv_web/live/member_live/form.ex:138
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -250,8 +254,8 @@ msgstr ""
|
||||||
msgid "Your password has successfully been reset"
|
msgid "Your password has successfully been reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:69
|
#: lib/mv_web/live/custom_field_live/form_component.ex:61
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:120
|
#: lib/mv_web/live/custom_field_live/index_component.ex:138
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
||||||
#: lib/mv_web/live/member_live/form.ex:81
|
#: lib/mv_web/live/member_live/form.ex:81
|
||||||
#: lib/mv_web/live/user_live/form.ex:251
|
#: lib/mv_web/live/user_live/form.ex:251
|
||||||
|
|
@ -264,7 +268,8 @@ msgstr ""
|
||||||
msgid "Choose a member"
|
msgid "Choose a member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:61
|
#: lib/mv_web/live/custom_field_live/form_component.ex:50
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:59
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -284,7 +289,7 @@ msgstr ""
|
||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:62
|
#: lib/mv_web/live/custom_field_live/form_component.ex:51
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -312,7 +317,8 @@ msgstr ""
|
||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:51
|
#: lib/mv_web/live/custom_field_live/form_component.ex:40
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:53
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -355,7 +361,7 @@ msgstr ""
|
||||||
msgid "Profil"
|
msgid "Profil"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:63
|
#: lib/mv_web/live/custom_field_live/form_component.ex:52
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -370,7 +376,10 @@ msgstr ""
|
||||||
msgid "Select member"
|
msgid "Select member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/components/layouts/navbar.ex:26
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:99
|
#: lib/mv_web/components/layouts/navbar.ex:99
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:32
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:42
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -411,7 +420,7 @@ msgstr ""
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:56
|
#: lib/mv_web/live/custom_field_live/form_component.ex:45
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -619,7 +628,7 @@ msgstr ""
|
||||||
msgid "Custom field"
|
msgid "Custom field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:117
|
#: lib/mv_web/live/global_settings_live.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Custom field %{action} successfully"
|
msgid "Custom field %{action} successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -634,7 +643,7 @@ msgstr ""
|
||||||
msgid "Please select a custom field first"
|
msgid "Please select a custom field first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:67
|
#: lib/mv_web/live/custom_field_live/form_component.ex:64
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Save Custom field"
|
msgid "Save Custom field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -644,12 +653,7 @@ msgstr ""
|
||||||
msgid "Save Custom field value"
|
msgid "Save Custom field value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:46
|
#: lib/mv_web/live/custom_field_live/index_component.ex:20
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Use this form to manage custom_field records in your database."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:26
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Custom Fields"
|
msgid "Custom Fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -659,70 +663,64 @@ msgstr ""
|
||||||
msgid "Use this form to manage Custom Field Value records in your database."
|
msgid "Use this form to manage Custom Field Value records in your database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/show.ex:56
|
#: lib/mv_web/live/custom_field_live/index_component.ex:97
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Auto-generated identifier (immutable)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:79
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "%{count} member has a value assigned for this custom field."
|
msgid "%{count} member has a value assigned for this custom field."
|
||||||
msgid_plural "%{count} members have values assigned for this custom field."
|
msgid_plural "%{count} members have values assigned for this custom field."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:87
|
#: lib/mv_web/live/custom_field_live/index_component.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:72
|
#: lib/mv_web/live/custom_field_live/index_component.ex:90
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field"
|
msgid "Delete Custom Field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:127
|
#: lib/mv_web/live/custom_field_live/index_component.ex:146
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field and All Values"
|
msgid "Delete Custom Field and All Values"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:109
|
#: lib/mv_web/live/custom_field_live/index_component.ex:127
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enter the text above to confirm"
|
msgid "Enter the text above to confirm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:97
|
#: lib/mv_web/live/custom_field_live/index_component.ex:115
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "To confirm deletion, please enter this text:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:64
|
#: lib/mv_web/live/custom_field_live/form_component.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:51
|
#: lib/mv_web/live/global_settings_live.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Association Name"
|
msgid "Association Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:31
|
#: lib/mv_web/live/global_settings_live.ex:50
|
||||||
#: lib/mv_web/live/global_settings_live.ex:41
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Club Settings"
|
msgid "Club Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:43
|
#: lib/mv_web/live/global_settings_live.ex:44
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Manage global settings for the association."
|
msgid "Manage global settings for the association."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:56
|
#: lib/mv_web/live/global_settings_live.ex:61
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Save Settings"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:75
|
#: lib/mv_web/live/global_settings_live.ex:87
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings updated successfully"
|
msgid "Settings updated successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -853,3 +851,48 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Payment filter"
|
msgid "Payment filter"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:110
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Custom field deleted successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Edit Custom Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:119
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to delete custom field: %{error}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "New Custom Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:26
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "New Custom field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:63
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Show in Overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:125
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Slug does not match. Deletion cancelled."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:55
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Value Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "These will appear in addition to other data when adding new members."
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,14 @@ msgstr ""
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:82
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:250
|
#: lib/mv_web/live/member_live/index.html.heex:250
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:74
|
#: lib/mv_web/live/user_live/index.html.heex:74
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:76
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:242
|
#: 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/form.ex:265
|
||||||
#: lib/mv_web/live/user_live/index.html.heex:66
|
#: lib/mv_web/live/user_live/index.html.heex:66
|
||||||
|
|
@ -156,9 +158,9 @@ msgstr ""
|
||||||
msgid "Save Member"
|
msgid "Save Member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:66
|
#: lib/mv_web/live/custom_field_live/form_component.ex:63
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
#: lib/mv_web/live/custom_field_value_live/form.ex:74
|
||||||
#: lib/mv_web/live/global_settings_live.ex:55
|
#: lib/mv_web/live/global_settings_live.ex:60
|
||||||
#: lib/mv_web/live/member_live/form.ex:78
|
#: lib/mv_web/live/member_live/form.ex:78
|
||||||
#: lib/mv_web/live/user_live/form.ex:248
|
#: lib/mv_web/live/user_live/form.ex:248
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -177,6 +179,7 @@ msgstr ""
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:68
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:61
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -194,6 +197,7 @@ msgstr ""
|
||||||
msgid "This is a member record from your database."
|
msgid "This is a member record from your database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:65
|
||||||
#: lib/mv_web/live/member_live/index.html.heex:234
|
#: 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/index/formatter.ex:60
|
||||||
#: lib/mv_web/live/member_live/show.ex:52
|
#: lib/mv_web/live/member_live/show.ex:52
|
||||||
|
|
@ -201,14 +205,14 @@ msgstr ""
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:110
|
#: lib/mv_web/live/custom_field_live/form_component.ex:93
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
#: lib/mv_web/live/custom_field_value_live/form.ex:233
|
||||||
#: lib/mv_web/live/member_live/form.ex:137
|
#: lib/mv_web/live/member_live/form.ex:137
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:111
|
#: lib/mv_web/live/custom_field_live/form_component.ex:94
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
#: lib/mv_web/live/custom_field_value_live/form.ex:234
|
||||||
#: lib/mv_web/live/member_live/form.ex:138
|
#: lib/mv_web/live/member_live/form.ex:138
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -250,8 +254,8 @@ msgstr ""
|
||||||
msgid "Your password has successfully been reset"
|
msgid "Your password has successfully been reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:69
|
#: lib/mv_web/live/custom_field_live/form_component.ex:61
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:120
|
#: lib/mv_web/live/custom_field_live/index_component.ex:138
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
#: lib/mv_web/live/custom_field_value_live/form.ex:77
|
||||||
#: lib/mv_web/live/member_live/form.ex:81
|
#: lib/mv_web/live/member_live/form.ex:81
|
||||||
#: lib/mv_web/live/user_live/form.ex:251
|
#: lib/mv_web/live/user_live/form.ex:251
|
||||||
|
|
@ -264,7 +268,8 @@ msgstr ""
|
||||||
msgid "Choose a member"
|
msgid "Choose a member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:61
|
#: lib/mv_web/live/custom_field_live/form_component.ex:50
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:59
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -284,7 +289,7 @@ msgstr ""
|
||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:62
|
#: lib/mv_web/live/custom_field_live/form_component.ex:51
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -312,7 +317,8 @@ msgstr ""
|
||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:51
|
#: lib/mv_web/live/custom_field_live/form_component.ex:40
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:53
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -355,7 +361,7 @@ msgstr ""
|
||||||
msgid "Profil"
|
msgid "Profil"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:63
|
#: lib/mv_web/live/custom_field_live/form_component.ex:52
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -370,7 +376,10 @@ msgstr ""
|
||||||
msgid "Select member"
|
msgid "Select member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/components/layouts/navbar.ex:26
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:99
|
#: lib/mv_web/components/layouts/navbar.ex:99
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:32
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:42
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -411,7 +420,7 @@ msgstr ""
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:56
|
#: lib/mv_web/live/custom_field_live/form_component.ex:45
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -619,7 +628,7 @@ msgstr ""
|
||||||
msgid "Custom field"
|
msgid "Custom field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:117
|
#: lib/mv_web/live/global_settings_live.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Custom field %{action} successfully"
|
msgid "Custom field %{action} successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -634,7 +643,7 @@ msgstr ""
|
||||||
msgid "Please select a custom field first"
|
msgid "Please select a custom field first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:67
|
#: lib/mv_web/live/custom_field_live/form_component.ex:64
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Save Custom field"
|
msgid "Save Custom field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -644,12 +653,7 @@ msgstr ""
|
||||||
msgid "Save Custom field value"
|
msgid "Save Custom field value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:46
|
#: lib/mv_web/live/custom_field_live/index_component.ex:20
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
|
||||||
msgid "Use this form to manage custom_field records in your database."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/navbar.ex:26
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Custom Fields"
|
msgid "Custom Fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -659,70 +663,64 @@ msgstr ""
|
||||||
msgid "Use this form to manage Custom Field Value records in your database."
|
msgid "Use this form to manage Custom Field Value records in your database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/show.ex:56
|
#: lib/mv_web/live/custom_field_live/index_component.ex:97
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Auto-generated identifier (immutable)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:79
|
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "%{count} member has a value assigned for this custom field."
|
msgid "%{count} member has a value assigned for this custom field."
|
||||||
msgid_plural "%{count} members have values assigned for this custom field."
|
msgid_plural "%{count} members have values assigned for this custom field."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:87
|
#: lib/mv_web/live/custom_field_live/index_component.ex:105
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
msgid "All custom field values will be permanently deleted when you delete this custom field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:72
|
#: lib/mv_web/live/custom_field_live/index_component.ex:90
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field"
|
msgid "Delete Custom Field"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:127
|
#: lib/mv_web/live/custom_field_live/index_component.ex:146
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Delete Custom Field and All Values"
|
msgid "Delete Custom Field and All Values"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:109
|
#: lib/mv_web/live/custom_field_live/index_component.ex:127
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enter the text above to confirm"
|
msgid "Enter the text above to confirm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index.ex:97
|
#: lib/mv_web/live/custom_field_live/index_component.ex:115
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "To confirm deletion, please enter this text:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form.ex:64
|
#: lib/mv_web/live/custom_field_live/form_component.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:51
|
#: lib/mv_web/live/global_settings_live.ex:56
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Association Name"
|
msgid "Association Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:31
|
#: lib/mv_web/live/global_settings_live.ex:50
|
||||||
#: lib/mv_web/live/global_settings_live.ex:41
|
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Club Settings"
|
msgid "Club Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:43
|
#: lib/mv_web/live/global_settings_live.ex:44
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Manage global settings for the association."
|
msgid "Manage global settings for the association."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:56
|
#: lib/mv_web/live/global_settings_live.ex:61
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Save Settings"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/global_settings_live.ex:75
|
#: lib/mv_web/live/global_settings_live.ex:87
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Settings updated successfully"
|
msgid "Settings updated successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -854,8 +852,63 @@ msgstr ""
|
||||||
msgid "Payment filter"
|
msgid "Payment filter"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:110
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Custom field deleted successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Edit Custom Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:119
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to delete custom field: %{error}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/form_component.ex:29
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "New Custom Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:26
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "New Custom field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:63
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Show in Overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex:125
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Slug does not match. Deletion cancelled."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:55
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Value Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "These will appear in addition to other data when adding new members."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/custom_field_live/show.ex:56
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Auto-generated identifier (immutable)"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/member_live/form.ex:48
|
#~ #: lib/mv_web/live/member_live/form.ex:48
|
||||||
#~ #: lib/mv_web/live/member_live/show.ex:51
|
#~ #: lib/mv_web/live/member_live/show.ex:51
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Birth Date"
|
#~ msgid "Birth Date"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/custom_field_live/form.ex:46
|
||||||
|
#~ #, elixir-autogen, elixir-format, fuzzy
|
||||||
|
#~ msgid "Use this form to manage custom_field records in your database."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
defmodule Mv.Membership.MemberFieldVisibilityTest do
|
|
||||||
@moduledoc """
|
|
||||||
Tests for member field visibility configuration.
|
|
||||||
|
|
||||||
Tests cover:
|
|
||||||
- Member fields are visible by default (show_in_overview: true)
|
|
||||||
- Member fields can be hidden (show_in_overview: false)
|
|
||||||
- Checking if a specific field is visible
|
|
||||||
- Configuration is stored in Settings resource
|
|
||||||
"""
|
|
||||||
use Mv.DataCase, async: true
|
|
||||||
|
|
||||||
alias Mv.Membership.Member
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule MvWeb.CustomFieldLive.DeletionTest do
|
defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Tests for CustomFieldLive.Index deletion modal and slug confirmation.
|
Tests for CustomFieldLive.IndexComponent deletion modal and slug confirmation.
|
||||||
|
Tests the custom field management component embedded in the settings page.
|
||||||
|
|
||||||
Tests cover:
|
Tests cover:
|
||||||
- Opening deletion confirmation modal
|
- Opening deletion confirmation modal
|
||||||
|
|
@ -39,11 +40,11 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
# Create custom field value
|
# Create custom field value
|
||||||
create_custom_field_value(member, custom_field, "test")
|
create_custom_field_value(member, custom_field, "test")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
# Click delete button
|
# Click delete button - find the delete link within the component
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Modal should be visible
|
# Modal should be visible
|
||||||
|
|
@ -65,10 +66,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
create_custom_field_value(member1, custom_field, "test1")
|
create_custom_field_value(member1, custom_field, "test1")
|
||||||
create_custom_field_value(member2, custom_field, "test2")
|
create_custom_field_value(member2, custom_field, "test2")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Should show plural form
|
# Should show plural form
|
||||||
|
|
@ -78,10 +79,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
test "shows 0 members for custom field without values", %{conn: conn} do
|
test "shows 0 members for custom field without values", %{conn: conn} do
|
||||||
{:ok, _custom_field} = create_custom_field("test_field", :string)
|
{:ok, _custom_field} = create_custom_field("test_field", :string)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Should show 0 members
|
# Should show 0 members
|
||||||
|
|
@ -93,15 +94,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
test "updates confirmation state when typing", %{conn: conn} do
|
test "updates confirmation state when typing", %{conn: conn} do
|
||||||
{:ok, custom_field} = create_custom_field("test_field", :string)
|
{:ok, custom_field} = create_custom_field("test_field", :string)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Type in slug input
|
# Type in slug input - use element to find the form with phx-target
|
||||||
view
|
view
|
||||||
|> render_change("update_slug_confirmation", %{"slug" => custom_field.slug})
|
|> element("#delete-custom-field-modal form")
|
||||||
|
|> render_change(%{"slug" => custom_field.slug})
|
||||||
|
|
||||||
# Confirm button should be enabled now (no disabled attribute)
|
# Confirm button should be enabled now (no disabled attribute)
|
||||||
html = render(view)
|
html = render(view)
|
||||||
|
|
@ -111,15 +113,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
test "delete button is disabled when slug doesn't match", %{conn: conn} do
|
test "delete button is disabled when slug doesn't match", %{conn: conn} do
|
||||||
{:ok, _custom_field} = create_custom_field("test_field", :string)
|
{:ok, _custom_field} = create_custom_field("test_field", :string)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Type wrong slug
|
# Type wrong slug - use element to find the form with phx-target
|
||||||
view
|
view
|
||||||
|> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"})
|
|> element("#delete-custom-field-modal form")
|
||||||
|
|> render_change(%{"slug" => "wrong-slug"})
|
||||||
|
|
||||||
# Button should be disabled
|
# Button should be disabled
|
||||||
html = render(view)
|
html = render(view)
|
||||||
|
|
@ -133,20 +136,21 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
{:ok, custom_field} = create_custom_field("test_field", :string)
|
{:ok, custom_field} = create_custom_field("test_field", :string)
|
||||||
{:ok, custom_field_value} = create_custom_field_value(member, custom_field, "test")
|
{:ok, custom_field_value} = create_custom_field_value(member, custom_field, "test")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
# Open modal
|
# Open modal
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Enter correct slug
|
# Enter correct slug - use element to find the form with phx-target
|
||||||
view
|
view
|
||||||
|> render_change("update_slug_confirmation", %{"slug" => custom_field.slug})
|
|> element("#delete-custom-field-modal form")
|
||||||
|
|> render_change(%{"slug" => custom_field.slug})
|
||||||
|
|
||||||
# Click confirm
|
# Click confirm
|
||||||
view
|
view
|
||||||
|> element("button", "Delete Custom Field and All Values")
|
|> element("#delete-custom-field-modal button", "Delete Custom Field and All Values")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Should show success message
|
# Should show success message
|
||||||
|
|
@ -162,27 +166,28 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
assert {:ok, _} = Ash.get(Member, member.id)
|
assert {:ok, _} = Ash.get(Member, member.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "shows error when slug doesn't match", %{conn: conn} do
|
test "button remains disabled and custom field not deleted when slug doesn't match", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
{:ok, custom_field} = create_custom_field("test_field", :string)
|
{:ok, custom_field} = create_custom_field("test_field", :string)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Enter wrong slug
|
# Enter wrong slug - use element to find the form with phx-target
|
||||||
view
|
view
|
||||||
|> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"})
|
|> element("#delete-custom-field-modal form")
|
||||||
|
|> render_change(%{"slug" => "wrong-slug"})
|
||||||
|
|
||||||
# Try to confirm (button should be disabled, but test the handler anyway)
|
# Button should be disabled and we cannot click it
|
||||||
view
|
# The test verifies that the button is properly disabled in the UI
|
||||||
|> render_click("confirm_delete", %{})
|
html = render(view)
|
||||||
|
assert html =~ ~r/disabled(?:=""|(?!\w))/
|
||||||
|
|
||||||
# Should show error message
|
# Custom field should still exist since deletion couldn't proceed
|
||||||
assert render(view) =~ "Slug does not match"
|
|
||||||
|
|
||||||
# Custom field should still exist
|
|
||||||
assert {:ok, _} = Ash.get(CustomField, custom_field.id)
|
assert {:ok, _} = Ash.get(CustomField, custom_field.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -191,10 +196,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
test "closes modal without deleting", %{conn: conn} do
|
test "closes modal without deleting", %{conn: conn} do
|
||||||
{:ok, custom_field} = create_custom_field("test_field", :string)
|
{:ok, custom_field} = create_custom_field("test_field", :string)
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/custom_fields")
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("a", "Delete")
|
|> element("#custom-fields-component a", "Delete")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Modal should be visible
|
# Modal should be visible
|
||||||
|
|
@ -202,7 +207,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
||||||
|
|
||||||
# Click cancel
|
# Click cancel
|
||||||
view
|
view
|
||||||
|> element("button", "Cancel")
|
|> element("#delete-custom-field-modal button", "Cancel")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
# Modal should be gone
|
# Modal should be gone
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,6 @@ defmodule MvWeb.ProfileNavigationTest do
|
||||||
# Verify we're on the correct profile page with OIDC specific information
|
# Verify we're on the correct profile page with OIDC specific information
|
||||||
{:ok, _profile_view, html} = live(conn, "/users/#{user.id}")
|
{:ok, _profile_view, html} = live(conn, "/users/#{user.id}")
|
||||||
assert html =~ to_string(user.email)
|
assert html =~ to_string(user.email)
|
||||||
# OIDC ID should be visible
|
|
||||||
assert html =~ "oidc_123"
|
|
||||||
# Password auth should be disabled for OIDC users
|
# Password auth should be disabled for OIDC users
|
||||||
assert html =~ "Not enabled"
|
assert html =~ "Not enabled"
|
||||||
end
|
end
|
||||||
|
|
@ -150,8 +148,6 @@ defmodule MvWeb.ProfileNavigationTest do
|
||||||
"/members/new",
|
"/members/new",
|
||||||
"/custom_field_values",
|
"/custom_field_values",
|
||||||
"/custom_field_values/new",
|
"/custom_field_values/new",
|
||||||
"/custom_fields",
|
|
||||||
"/custom_fields/new",
|
|
||||||
"/users",
|
"/users",
|
||||||
"/users/new"
|
"/users/new"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -231,8 +231,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest 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")
|
||||||
|
|
||||||
# Date should be displayed in readable format
|
# Date should be displayed in European format (dd.mm.yyyy)
|
||||||
assert html =~ "1990" or html =~ "1990-05-15" or html =~ "15.05.1990"
|
assert html =~ "15.05.1990"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "formats email custom field values correctly", %{conn: conn, member1: _member1} do
|
test "formats email custom field values correctly", %{conn: conn, member1: _member1} do
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,6 @@ defmodule MvWeb.UserLive.IndexTest do
|
||||||
|
|
||||||
assert html =~ "alice@example.com"
|
assert html =~ "alice@example.com"
|
||||||
assert html =~ "bob@example.com"
|
assert html =~ "bob@example.com"
|
||||||
assert html =~ "alice123"
|
|
||||||
assert html =~ "bob456"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "shows correct action links", %{conn: conn} do
|
test "shows correct action links", %{conn: conn} do
|
||||||
|
|
@ -386,10 +384,6 @@ defmodule MvWeb.UserLive.IndexTest do
|
||||||
|
|
||||||
# Should still show the table structure
|
# Should still show the table structure
|
||||||
assert html =~ "Email"
|
assert html =~ "Email"
|
||||||
assert html =~ "OIDC ID"
|
|
||||||
# Should show the authenticated user at minimum
|
|
||||||
# Matches the generated email pattern oidc.user{unique_id}@example.com
|
|
||||||
assert html =~ "oidc.user"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles users with missing OIDC ID", %{conn: conn} do
|
test "handles users with missing OIDC ID", %{conn: conn} do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue