Cleanup of the docs closes #507 #530
5 changed files with 113 additions and 731 deletions
|
|
@ -1,88 +1,53 @@
|
||||||
# Phase 1 — Badge WCAG Analysis & Migration
|
# Badge Component Design Notes (WCAG)
|
||||||
|
|
||||||
## 1) Repo-Analyse (Stand vor Änderungen)
|
Design rationale for the central `<.badge>` core component
|
||||||
|
(`MvWeb.CoreComponents`) and the WCAG-driven theme overrides in
|
||||||
|
`assets/css/app.css`. Before it, badges were raw `<span class="badge ...">`
|
||||||
|
markup with no central component.
|
||||||
|
|
||||||
### Badge-Verwendungen (alle Fundstellen)
|
## `<.badge>` API contract
|
||||||
|
|
||||||
| Datei | Kontext | Markup |
|
- `attr :variant` — `:neutral | :primary | :info | :success | :warning | :error`
|
||||||
|-------|---------|--------|
|
- `attr :style` — `:soft | :solid | :outline` (default: `:soft`)
|
||||||
| `lib/mv_web/live/member_field_live/index_component.ex` | Tabelle (show_in_overview) | `<span class="badge badge-success">` / `<span class="badge badge-ghost">` |
|
- `attr :size` — `:sm | :md` (default: `:md`)
|
||||||
| `lib/mv_web/live/components/member_filter_component.ex` | Filter-Chips (Anzahl) | `<span class="badge badge-primary badge-sm">` (2×) |
|
- `attr :sr_label` — optional screen-reader label for icon-only badges
|
||||||
| `lib/mv_web/live/role_live/index.html.heex` | Tabelle (System Role, Permission Set, Custom) | `badge-warning`, `permission_set_badge_class()`, `badge-ghost` (User Count) |
|
- `slot :icon` — optional
|
||||||
| `lib/mv_web/helpers/membership_fee_helpers.ex` | Helper | `status_color/1` → "badge-success" \| "badge-error" \| "badge-ghost" |
|
- `slot :inner_block` — badge text
|
||||||
| `lib/mv_web/live/member_live/show.ex` | Mitgliedsdetail (Beiträge) | `<span class={["badge", status_color(status)]}>`, `badge-ghost` (No cycles) |
|
|
||||||
| `lib/mv_web/live/membership_fee_settings_live.ex` | Settings (Fee Types) | `badge-outline`, `badge-ghost` (member count) |
|
|
||||||
| `lib/mv_web/live/membership_fee_type_live/index.ex` | Index (Fee Types) | `badge-outline`, `badge-ghost` (member count) |
|
|
||||||
| `lib/mv_web/live/role_live/index.ex` | (Helper-Import) | `permission_set_badge_class/1` |
|
|
||||||
| `lib/mv_web/live/member_live/show/membership_fees_component.ex` | Mitgliedsbeiträge | `badge-outline`, `["badge", status_color]` |
|
|
||||||
| `lib/mv_web/live/custom_field_live/index_component.ex` | Tabelle (show_in_overview) | `badge-success`, `badge-ghost` |
|
|
||||||
| `lib/mv_web/member_live/index/membership_fee_status.ex` | Helper | `format_cycle_status_badge/1` → map mit `color`, `icon`, `label` |
|
|
||||||
| `lib/mv_web/live/global_settings_live.ex` | Form (label-text-alt) | `badge badge-ghost` "(set)" (2×) |
|
|
||||||
| `lib/mv_web/live/member_live/index.html.heex` | Tabelle (Status) | `format_cycle_status_badge` + `<span class={["badge", badge.color]}>`, `badge-ghost` (No cycle), `badge-outline badge-primary` (Filter-Chip) |
|
|
||||||
| `lib/mv_web/live/role_live/helpers.ex` | Helper | `permission_set_badge_class/1` → "badge badge-* badge-sm" |
|
|
||||||
| `lib/mv_web/live/group_live/show.ex` | Card | `badge badge-outline badge` |
|
|
||||||
| `lib/mv_web/live/role_live/show.ex` | Detail | `permission_set_badge_class`, `badge-warning` (System), `badge-ghost` (No) |
|
|
||||||
|
|
||||||
### DaisyUI/Tailwind Config
|
## Design rules
|
||||||
|
|
||||||
- **Tailwind:** `assets/tailwind.config.js` — erweitert nur `theme.extend.colors.brand`; kein DaisyUI hier.
|
- `:soft` and `:solid` use a visible background; `:soft` is the default. No
|
||||||
- **DaisyUI:** wird in `assets/css/app.css` per `@plugin "../vendor/daisyui"` mit `themes: false` geladen.
|
transparent ghost as a default.
|
||||||
- **Themes:** Zwei Custom-Themes in `app.css`:
|
- `:outline` **always** sets a background (e.g. `bg-base-100`) so the border
|
||||||
- `@plugin "../vendor/daisyui-theme"` mit `name: "dark"` (default: false)
|
stays visible on grey (`base-200`/`base-300`) surfaces.
|
||||||
- `@plugin "../vendor/daisyui-theme"` mit `name: "light"` (default: true)
|
- Ghost only as an explicit opt-in, and then with `bg-base-100` for visibility
|
||||||
- **Theme-Umschaltung:** `lib/mv_web/components/layouts/root.html.heex` — Inline-Script setzt `document.documentElement.setAttribute("data-theme", "light"|"dark")` aus `localStorage["phx:theme"]` oder `prefers-color-scheme`. Sidebar enthält Theme-Toggle (`<.theme_toggle />`).
|
(plain DaisyUI `badge-ghost` is `base-200` on `base-200` — nearly invisible).
|
||||||
|
- Clickable chips keep `<.badge>` as a plain container with a button in the
|
||||||
|
`inner_block`.
|
||||||
|
|
||||||
### Core Components
|
## WCAG contrast overrides (`app.css`)
|
||||||
|
|
||||||
- **Modul:** `lib/mv_web/components/core_components.ex` (MvWeb.CoreComponents).
|
DaisyUI defaults do not reach the WCAG 2.2 AA **4.5:1** text-contrast ratio for
|
||||||
- **Vorhanden:** flash, button, dropdown_menu, form_section, input, header, table, icon, link, etc.
|
badges, so `app.css` adds per-theme overrides on top of the custom `light` /
|
||||||
- **Badge:** Bisher keine zentrale `<.badge>`-Komponente.
|
`dark` themes:
|
||||||
|
|
||||||
### DaisyUI Badge (Vendor)
|
- **Light theme:** darker `--badge-fg` for all solid variants; darker text on
|
||||||
|
`badge-soft`'s tinted background; uniform dark text for `badge-outline` on
|
||||||
|
`base-100`.
|
||||||
|
- **Dark theme:** slightly darkened solid-badge backgrounds so the light
|
||||||
|
`*-content` colors reach 4.5:1; lighter, readable variant tints for
|
||||||
|
`badge-soft`; light `--badge-fg` for `badge-outline` on `base-100`.
|
||||||
|
|
||||||
- **Default:** `--badge-bg: var(--badge-color, var(--color-base-100))`, `--badge-fg: var(--color-base-content)`.
|
Related: contrast overrides for the member-filter join buttons
|
||||||
- **badge-outline:** `--badge-bg: "#0000"` (transparent) → Kontrastproblem auf base-200/base-300.
|
(`.member-filter-dropdown .join .btn`) under the same 4.5:1 rule.
|
||||||
- **badge-ghost:** `background-color: var(--color-base-200)`, `color: var(--color-base-content)` → auf base-200-Flächen kaum sichtbar.
|
|
||||||
- **badge-soft:** color-mix 8% Variante mit base-100 → sichtbar; Text ist Variantenfarbe (Kontrast prüfen).
|
|
||||||
|
|
||||||
---
|
## Variant helpers
|
||||||
|
|
||||||
## 2) Core Component <.badge> API (geplant)
|
- `MembershipFeeHelpers.status_variant/1` → `:success | :error | :warning`.
|
||||||
|
`status_variant(:suspended) -> :warning` (yellow) deliberately matches the
|
||||||
- **attr :variant** — `:neutral | :primary | :info | :success | :warning | :error`
|
Edit button (`btn-warning`), keeping the "suspended" badge the same color as
|
||||||
- **attr :style** — `:soft | :solid | :outline` (Default: `:soft`)
|
its action.
|
||||||
- **attr :size** — `:sm | :md` (Default: `:md`)
|
- `RoleLive.Helpers.permission_set_badge_variant/1` →
|
||||||
- **slot :inner_block** — Badge-Text
|
`:neutral | :info | :success | :error`.
|
||||||
- **attr :sr_label** — optional, für Icon-only (Screen Reader)
|
- `MembershipFeeStatus.format_cycle_status_badge/1` additionally returns a
|
||||||
- **slot :icon** — optional
|
`:variant` for `<.badge>`.
|
||||||
|
|
||||||
Regeln:
|
|
||||||
|
|
||||||
- `:soft` und `:solid` nutzen sichtbaren Hintergrund (kein transparenter Ghost als Default).
|
|
||||||
- `:outline` setzt immer einen Hintergrund (z. B. `bg-base-100`), damit der Rand auf grauen Flächen sichtbar bleibt.
|
|
||||||
- Ghost nur als explizites Opt-in; dann mit `bg-base-100` für Sichtbarkeit.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3) Theme-Overrides (WCAG)
|
|
||||||
|
|
||||||
- In `app.css` sind bereits Custom-Themes für `light` und `dark` mit eigenen Tokens.
|
|
||||||
- **Badge-Kontrast (WCAG 2.2 AA 4.5:1):** Zusätzliche Overrides in `app.css`:
|
|
||||||
- **Light theme:** Dunkle `--badge-fg` für alle Varianten (primary, success, error, warning, info, neutral); für `badge-soft` dunklere Textfarbe (`color`) auf getöntem Hintergrund; für `badge-outline` einheitlich dunkle Schrift auf base-100.
|
|
||||||
- **Dark theme:** Leicht abgedunkelte Badge-Hintergründe für Solid-Badges, damit die hellen *-content-Farben 4.5:1 erreichen; für `badge-soft` hellere, gut lesbare Variantentöne; für `badge-outline` heller Text (`--badge-fg`) auf base-100.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4) Migration (erledigt)
|
|
||||||
|
|
||||||
- Alle `<span class="badge ...">` durch `<.badge variant="..." style="...">...</.badge>` ersetzt.
|
|
||||||
- Klickbare Chips (z. B. Group Show „Remove“) bleiben als <.badge> mit Button im inner_block (Badge ist nur Container).
|
|
||||||
- **Neue Helper:** `MembershipFeeHelpers.status_variant/1` (→ :success | :error | :warning; suspended = :warning wie Edit-Button), `RoleLive.Helpers.permission_set_badge_variant/1` (→ :neutral | :info | :success | :error).
|
|
||||||
- **Angepasst:** `MembershipFeeStatus.format_cycle_status_badge/1` liefert zusätzlich `:variant` für <.badge>.
|
|
||||||
- **Migrierte Stellen:** member_field_live, member_filter_component, role_live (index + show), member_live (show, index, membership_fees_component), membership_fee_settings_live, membership_fee_type_live, custom_field_live, global_settings_live, group_live/show.
|
|
||||||
|
|
||||||
## 5) Weitere Anpassungen (nach Phase 1)
|
|
||||||
|
|
||||||
- **Filter Join-Buttons (WCAG):** In `app.css` Kontrast-Overrides für `.member-filter-dropdown .join .btn` (inaktiv: base-100/base-200 + dunkle/helle Schrift; aktiv: success/error mit 4.5:1).
|
|
||||||
- **Badge „Pausiert“ (suspended):** `status_variant(:suspended)` → `:warning` (gelb), damit Badge dieselbe Farbe wie der Edit-Button (btn-warning) hat.
|
|
||||||
- **Filter-Dropdown schließen:** `phx-click-away` vom inneren Panel auf den äußeren Wrapper (`member-filter-dropdown`) verschoben; Klick auf den Filter-Button schließt das Dropdown (konsistent mit Spalten/Ausblenden).
|
|
||||||
|
|
|
||||||
|
|
@ -1,533 +1,21 @@
|
||||||
# DaisyUI Drawer Pattern - Standard Implementation
|
# Sidebar Drawer Pattern (project note)
|
||||||
|
|
||||||
This document describes the standard DaisyUI drawer pattern for implementing responsive sidebars. It covers mobile overlay drawers, desktop persistent sidebars, and their combination.
|
The app's responsive sidebar uses DaisyUI's drawer in the **combined
|
||||||
|
mobile-overlay + desktop-persistent** configuration. The drawer container,
|
||||||
## Core Concept
|
the `mobile-drawer` toggle checkbox and the mobile header live in the `app/1`
|
||||||
|
layout (`lib/mv_web/components/layouts.ex`); the sidebar body and the
|
||||||
DaisyUI's drawer component uses a **checkbox-based toggle mechanism** combined with CSS to create accessible, responsive sidebars without custom JavaScript.
|
tap-to-close `drawer-overlay` live in `lib/mv_web/components/layouts/sidebar.ex`.
|
||||||
|
|
||||||
### Key Components
|
## Chosen pattern
|
||||||
|
|
||||||
1. **`drawer`** - Container element
|
- `drawer lg:drawer-open` on the container — sidebar is a slide-in overlay on
|
||||||
2. **`drawer-toggle`** - Hidden checkbox that controls open/close state
|
mobile (`< lg`, 1024px) and permanently visible on desktop (`≥ lg`).
|
||||||
3. **`drawer-content`** - Main content area
|
- A hidden checkbox (`class="drawer-toggle"`, `id="mobile-drawer"`) holds the
|
||||||
4. **`drawer-side`** - Sidebar content (menu, navigation)
|
open/close state; `<label for="mobile-drawer">` elements toggle it. Pure CSS,
|
||||||
5. **`drawer-overlay`** - Optional overlay for mobile (closes drawer on click)
|
no JavaScript; the native checkbox provides keyboard accessibility.
|
||||||
|
- The mobile hamburger toggle and the `drawer-overlay` (tap-to-close) are
|
||||||
## HTML Structure
|
marked `lg:hidden`, so on desktop there is no toggle and no overlay — main
|
||||||
|
content shifts to make room for the fixed-width sidebar.
|
||||||
```html
|
|
||||||
<div class="drawer">
|
This is DaisyUI's standard recommended approach for responsive sidebars; see
|
||||||
<!-- Hidden checkbox controls the drawer state -->
|
the DaisyUI drawer docs for the full component API if extending it.
|
||||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
|
|
||||||
<!-- Main content area -->
|
|
||||||
<div class="drawer-content">
|
|
||||||
<!-- Page content goes here -->
|
|
||||||
<label for="my-drawer" class="btn btn-primary">Open drawer</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar content -->
|
|
||||||
<div class="drawer-side">
|
|
||||||
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
||||||
<ul class="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
|
|
||||||
<!-- Sidebar content goes here -->
|
|
||||||
<li><a>Sidebar Item 1</a></li>
|
|
||||||
<li><a>Sidebar Item 2</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## How drawer-toggle Works
|
|
||||||
|
|
||||||
### Mechanism
|
|
||||||
|
|
||||||
The `drawer-toggle` is a **hidden checkbox** that serves as the state controller:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
```
|
|
||||||
|
|
||||||
### Toggle Behavior
|
|
||||||
|
|
||||||
1. **Label Connection**: Any `<label for="my-drawer">` element can toggle the drawer
|
|
||||||
2. **Checkbox State**:
|
|
||||||
- `checked` → drawer is open
|
|
||||||
- `unchecked` → drawer is closed
|
|
||||||
3. **CSS Targeting**: DaisyUI uses CSS sibling selectors to show/hide the drawer based on checkbox state
|
|
||||||
4. **Accessibility**: Native checkbox provides keyboard accessibility (Space/Enter to toggle)
|
|
||||||
|
|
||||||
### Toggle Examples
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Button to open drawer -->
|
|
||||||
<label for="my-drawer" class="btn btn-primary drawer-button">
|
|
||||||
Open Menu
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- Close button inside drawer -->
|
|
||||||
<label for="my-drawer" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</label>
|
|
||||||
|
|
||||||
<!-- Overlay to close (click outside) -->
|
|
||||||
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mobile Drawer (Overlay)
|
|
||||||
|
|
||||||
### Characteristics
|
|
||||||
|
|
||||||
- Drawer slides in from the side (usually left)
|
|
||||||
- Overlays the main content
|
|
||||||
- Dark overlay (drawer-overlay) behind drawer
|
|
||||||
- Clicking overlay closes the drawer
|
|
||||||
- Typically used on mobile/tablet screens
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer">
|
|
||||||
<input id="mobile-drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
|
|
||||||
<div class="drawer-content">
|
|
||||||
<!-- Toggle button in header -->
|
|
||||||
<div class="navbar bg-base-100">
|
|
||||||
<div class="flex-none">
|
|
||||||
<label for="mobile-drawer" class="btn btn-square btn-ghost">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<a class="btn btn-ghost text-xl">My App</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
<div class="p-4">
|
|
||||||
<h1>Main Content</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-side">
|
|
||||||
<!-- Overlay - clicking it closes the drawer -->
|
|
||||||
<label for="mobile-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
||||||
|
|
||||||
<!-- Sidebar menu -->
|
|
||||||
<ul class="menu p-4 w-80 min-h-full bg-base-200">
|
|
||||||
<li><a>Home</a></li>
|
|
||||||
<li><a>About</a></li>
|
|
||||||
<li><a>Contact</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styling Notes
|
|
||||||
|
|
||||||
- **Width**: Default `w-80` (320px), adjust with Tailwind width utilities
|
|
||||||
- **Background**: Use DaisyUI color classes like `bg-base-200`
|
|
||||||
- **Height**: Always use `min-h-full` to ensure full height
|
|
||||||
- **Padding**: Add `p-4` or similar for inner spacing
|
|
||||||
|
|
||||||
## Desktop Sidebar (Persistent)
|
|
||||||
|
|
||||||
### Characteristics
|
|
||||||
|
|
||||||
- Always visible (no overlay)
|
|
||||||
- Does not overlay main content
|
|
||||||
- Main content adjusts to sidebar width
|
|
||||||
- No toggle button needed
|
|
||||||
- Used on desktop screens
|
|
||||||
|
|
||||||
### Implementation with drawer-open
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer lg:drawer-open">
|
|
||||||
<input id="desktop-drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
|
|
||||||
<div class="drawer-content">
|
|
||||||
<!-- Main content -->
|
|
||||||
<div class="p-4">
|
|
||||||
<h1>Main Content</h1>
|
|
||||||
<p>The sidebar is always visible on desktop (lg and above)</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-side">
|
|
||||||
<!-- No overlay needed for persistent sidebar -->
|
|
||||||
<label for="desktop-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
||||||
|
|
||||||
<!-- Sidebar menu -->
|
|
||||||
<ul class="menu p-4 w-80 min-h-full bg-base-200">
|
|
||||||
<li><a>Dashboard</a></li>
|
|
||||||
<li><a>Settings</a></li>
|
|
||||||
<li><a>Profile</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### How drawer-open Works
|
|
||||||
|
|
||||||
The `drawer-open` class forces the drawer to be **permanently open**:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer drawer-open">
|
|
||||||
```
|
|
||||||
|
|
||||||
- Drawer is always visible
|
|
||||||
- Cannot be toggled closed
|
|
||||||
- `drawer-toggle` checkbox is ignored
|
|
||||||
- `drawer-overlay` is not shown
|
|
||||||
- Main content automatically shifts to accommodate sidebar width
|
|
||||||
|
|
||||||
### Responsive Usage
|
|
||||||
|
|
||||||
Use Tailwind breakpoint modifiers for responsive behavior:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Open on large screens and above -->
|
|
||||||
<div class="drawer lg:drawer-open">
|
|
||||||
|
|
||||||
<!-- Open on medium screens and above -->
|
|
||||||
<div class="drawer md:drawer-open">
|
|
||||||
|
|
||||||
<!-- Open on extra-large screens and above -->
|
|
||||||
<div class="drawer xl:drawer-open">
|
|
||||||
```
|
|
||||||
|
|
||||||
## Combined Mobile + Desktop Pattern (Recommended)
|
|
||||||
|
|
||||||
This is the **most common pattern** for responsive applications: mobile overlay + desktop persistent.
|
|
||||||
|
|
||||||
### Complete Implementation
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer lg:drawer-open">
|
|
||||||
<!-- Checkbox for mobile toggle -->
|
|
||||||
<input id="app-drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
|
|
||||||
<div class="drawer-content flex flex-col">
|
|
||||||
<!-- Navbar with mobile menu button -->
|
|
||||||
<div class="navbar bg-base-100 lg:hidden">
|
|
||||||
<div class="flex-none">
|
|
||||||
<label for="app-drawer" class="btn btn-square btn-ghost">
|
|
||||||
<!-- Hamburger icon -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<a class="btn btn-ghost text-xl">My App</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
<div class="flex-1 p-6">
|
|
||||||
<h1 class="text-3xl font-bold mb-4">Welcome</h1>
|
|
||||||
<p>This is the main content area.</p>
|
|
||||||
<p>On mobile (< lg): sidebar is hidden, hamburger menu visible</p>
|
|
||||||
<p>On desktop (≥ lg): sidebar is persistent, hamburger menu hidden</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-side">
|
|
||||||
<!-- Overlay only shows on mobile -->
|
|
||||||
<label for="app-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
||||||
|
|
||||||
<!-- Sidebar navigation -->
|
|
||||||
<aside class="bg-base-200 w-80 min-h-full">
|
|
||||||
<!-- Logo/Header area -->
|
|
||||||
<div class="p-4 font-bold text-xl border-b border-base-300">
|
|
||||||
My App Logo
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation menu -->
|
|
||||||
<ul class="menu p-4">
|
|
||||||
<li><a>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
||||||
</svg>
|
|
||||||
Dashboard
|
|
||||||
</a></li>
|
|
||||||
<li><a>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
||||||
</svg>
|
|
||||||
Documents
|
|
||||||
</a></li>
|
|
||||||
<li><a>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
</svg>
|
|
||||||
Settings
|
|
||||||
</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Behavior Breakdown
|
|
||||||
|
|
||||||
#### On Mobile (< 1024px / < lg)
|
|
||||||
1. Sidebar is hidden by default
|
|
||||||
2. Hamburger button visible in navbar
|
|
||||||
3. Clicking hamburger opens sidebar as overlay
|
|
||||||
4. Clicking overlay or close button closes sidebar
|
|
||||||
5. Sidebar slides in from left with animation
|
|
||||||
|
|
||||||
#### On Desktop (≥ 1024px / ≥ lg)
|
|
||||||
1. `lg:drawer-open` keeps sidebar permanently visible
|
|
||||||
2. Hamburger button hidden via `lg:hidden`
|
|
||||||
3. Sidebar takes up fixed width (320px)
|
|
||||||
4. Main content area adjusts automatically
|
|
||||||
5. No overlay, no toggle needed
|
|
||||||
|
|
||||||
## Tailwind Breakpoints Reference
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* Default (mobile-first) */
|
|
||||||
/* < 640px */
|
|
||||||
|
|
||||||
sm: /* ≥ 640px */
|
|
||||||
md: /* ≥ 768px */
|
|
||||||
lg: /* ≥ 1024px */ ← Common desktop breakpoint
|
|
||||||
xl: /* ≥ 1280px */
|
|
||||||
2xl: /* ≥ 1536px */
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Classes Summary
|
|
||||||
|
|
||||||
| Class | Purpose |
|
|
||||||
|-------|---------|
|
|
||||||
| `drawer` | Main container |
|
|
||||||
| `drawer-toggle` | Hidden checkbox for state control |
|
|
||||||
| `drawer-content` | Main content area |
|
|
||||||
| `drawer-side` | Sidebar container |
|
|
||||||
| `drawer-overlay` | Clickable overlay (closes drawer) |
|
|
||||||
| `drawer-open` | Forces drawer to stay open |
|
|
||||||
| `drawer-end` | Positions drawer on the right side |
|
|
||||||
| `lg:drawer-open` | Opens drawer on large screens only |
|
|
||||||
|
|
||||||
## Positioning Variants
|
|
||||||
|
|
||||||
### Left Side Drawer (Default)
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer">
|
|
||||||
<!-- Drawer appears on the left -->
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Right Side Drawer
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer drawer-end">
|
|
||||||
<!-- Drawer appears on the right -->
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Accessibility
|
|
||||||
- Always include `aria-label` on overlay: `<label for="drawer" aria-label="close sidebar" class="drawer-overlay"></label>`
|
|
||||||
- Use semantic HTML (`<nav>`, `<aside>`)
|
|
||||||
- Ensure keyboard navigation works (native checkbox provides this)
|
|
||||||
|
|
||||||
### 2. Responsive Design
|
|
||||||
- Use `lg:drawer-open` for desktop persistence
|
|
||||||
- Hide mobile toggle button on desktop: `lg:hidden`
|
|
||||||
- Adjust sidebar width for mobile if needed: `w-64 md:w-80`
|
|
||||||
|
|
||||||
### 3. Performance
|
|
||||||
- DaisyUI drawer is pure CSS (no JavaScript needed)
|
|
||||||
- Animations are handled by CSS transitions
|
|
||||||
- No performance overhead
|
|
||||||
|
|
||||||
### 4. Styling
|
|
||||||
- Use DaisyUI theme colors: `bg-base-200`, `text-base-content`
|
|
||||||
- Maintain consistent spacing: `p-4`, `gap-2`
|
|
||||||
- Use DaisyUI menu component for navigation: `<ul class="menu">`
|
|
||||||
|
|
||||||
### 5. Content Structure
|
|
||||||
```html
|
|
||||||
<div class="drawer-content flex flex-col">
|
|
||||||
<!-- Navbar (if needed) -->
|
|
||||||
<div class="navbar">...</div>
|
|
||||||
|
|
||||||
<!-- Main content with flex-1 to fill space -->
|
|
||||||
<div class="flex-1 p-6">
|
|
||||||
<!-- Your content -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer (if needed) -->
|
|
||||||
<footer>...</footer>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Pattern 1: Drawer with Close Button
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="drawer-side">
|
|
||||||
<label for="drawer" class="drawer-overlay"></label>
|
|
||||||
<aside class="bg-base-200 w-80 min-h-full relative">
|
|
||||||
<!-- Close button (mobile only) -->
|
|
||||||
<label for="drawer" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2 lg:hidden">✕</label>
|
|
||||||
|
|
||||||
<!-- Sidebar content -->
|
|
||||||
<ul class="menu p-4 pt-12">
|
|
||||||
<li><a>Item 1</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 2: Drawer with User Profile
|
|
||||||
|
|
||||||
```html
|
|
||||||
<aside class="bg-base-200 w-80 min-h-full flex flex-col">
|
|
||||||
<!-- Logo -->
|
|
||||||
<div class="p-4 font-bold text-xl">My App</div>
|
|
||||||
|
|
||||||
<!-- Navigation (flex-1 to push footer down) -->
|
|
||||||
<ul class="menu flex-1 p-4">
|
|
||||||
<li><a>Dashboard</a></li>
|
|
||||||
<li><a>Settings</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- User profile footer -->
|
|
||||||
<div class="p-4 border-t border-base-300">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="avatar">
|
|
||||||
<div class="w-10 rounded-full">
|
|
||||||
<img src="/avatar.jpg" alt="User" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="font-semibold">John Doe</div>
|
|
||||||
<div class="text-sm opacity-70">john@example.com</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 3: Nested Menu with Submenu
|
|
||||||
|
|
||||||
```html
|
|
||||||
<ul class="menu p-4 w-80 min-h-full bg-base-200">
|
|
||||||
<li><a>Dashboard</a></li>
|
|
||||||
|
|
||||||
<!-- Submenu -->
|
|
||||||
<li>
|
|
||||||
<details>
|
|
||||||
<summary>Products</summary>
|
|
||||||
<ul>
|
|
||||||
<li><a>Electronics</a></li>
|
|
||||||
<li><a>Clothing</a></li>
|
|
||||||
<li><a>Books</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li><a>Settings</a></li>
|
|
||||||
</ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Drawer doesn't open on mobile
|
|
||||||
**Solution**: Check that:
|
|
||||||
1. Checkbox `id` matches label `for` attribute
|
|
||||||
2. Checkbox has class `drawer-toggle`
|
|
||||||
3. You're not using `drawer-open` on mobile breakpoints
|
|
||||||
|
|
||||||
### Issue: Drawer overlaps content on desktop
|
|
||||||
**Solution**:
|
|
||||||
- Remove `drawer-open` or use responsive variant `lg:drawer-open`
|
|
||||||
- Ensure you want overlay behavior, not persistent sidebar
|
|
||||||
|
|
||||||
### Issue: Overlay not clickable
|
|
||||||
**Solution**:
|
|
||||||
- Ensure overlay label has correct `for` attribute
|
|
||||||
- Check that overlay is not behind other elements (z-index)
|
|
||||||
|
|
||||||
### Issue: Content jumps when drawer opens
|
|
||||||
**Solution**:
|
|
||||||
- Add `flex flex-col` to `drawer-content`
|
|
||||||
- Ensure drawer-side width is fixed (e.g., `w-80`)
|
|
||||||
|
|
||||||
## Migration from Custom Solutions
|
|
||||||
|
|
||||||
If migrating from a custom sidebar implementation:
|
|
||||||
|
|
||||||
### Replace custom JavaScript
|
|
||||||
❌ Before:
|
|
||||||
```javascript
|
|
||||||
function toggleDrawer() {
|
|
||||||
document.getElementById('sidebar').classList.toggle('open');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
✅ After:
|
|
||||||
```html
|
|
||||||
<input id="drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
<label for="drawer">Toggle</label>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Replace custom CSS
|
|
||||||
❌ Before:
|
|
||||||
```css
|
|
||||||
.sidebar {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
.sidebar.open {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
✅ After:
|
|
||||||
```html
|
|
||||||
<div class="drawer">
|
|
||||||
<!-- DaisyUI handles all transitions -->
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Replace media query logic
|
|
||||||
❌ Before:
|
|
||||||
```css
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.sidebar { display: block; }
|
|
||||||
.toggle-button { display: none; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
✅ After:
|
|
||||||
```html
|
|
||||||
<div class="drawer lg:drawer-open">
|
|
||||||
<label for="drawer" class="lg:hidden">Toggle</label>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The DaisyUI drawer pattern provides:
|
|
||||||
|
|
||||||
✅ **Zero JavaScript** - Pure CSS solution
|
|
||||||
✅ **Accessible** - Built-in keyboard support via checkbox
|
|
||||||
✅ **Responsive** - Easy mobile/desktop variants with Tailwind
|
|
||||||
✅ **Themeable** - Uses DaisyUI theme colors
|
|
||||||
✅ **Flexible** - Supports left/right positioning
|
|
||||||
✅ **Standard** - No custom CSS needed
|
|
||||||
|
|
||||||
**Recommended approach**: Use `lg:drawer-open` for desktop with hidden mobile toggle for best responsive experience.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,38 @@
|
||||||
# Email Validation Strategy
|
# Email Validation Strategy
|
||||||
|
|
||||||
We use `EctoCommons.EmailValidator` with both `:html_input` and `:pow` checks, defined centrally in `Mv.Constants.email_validator_checks/0`.
|
We use `EctoCommons.EmailValidator` with **both** `:html_input` and `:pow`
|
||||||
|
checks, defined centrally in `Mv.Constants.email_validator_checks/0`
|
||||||
|
(`@email_validator_checks [:html_input, :pow]`).
|
||||||
|
|
||||||
## Checks Used
|
## Why both checks
|
||||||
|
|
||||||
- `:html_input` - Pragmatic validation matching browser `<input type="email">` behavior
|
- `:html_input` — pragmatic validation matching browser `<input type="email">`
|
||||||
- `:pow` - Stricter validation following email spec, supports internationalization (Unicode)
|
behavior; accepts the common formats users expect from web forms.
|
||||||
|
- `:pow` — stricter, spec-following validation (RFC 5322 and related);
|
||||||
|
supports international (Unicode) email addresses.
|
||||||
|
|
||||||
## Rationale
|
Using both balances user experience (accepting common formats) against
|
||||||
|
technical correctness (validating against email standards) and international
|
||||||
Using both checks ensures:
|
support.
|
||||||
- **Compatibility with common email providers** (`:html_input`) - Matches what users expect from web forms
|
|
||||||
- **Compliance with email standards** (`:pow`) - Follows RFC 5322 and related specifications
|
|
||||||
- **Support for international email addresses** (`:pow`) - Allows Unicode characters in email addresses
|
|
||||||
|
|
||||||
This dual approach provides a balance between user experience (accepting common email formats) and technical correctness (validating against email standards).
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The checks are used consistently across all email validation points:
|
The checks are applied consistently at every validation point, all reading the
|
||||||
|
single central constant so they stay in sync:
|
||||||
|
|
||||||
- `Mv.Membership.Import.MemberCSV.validate_row/3` - CSV import validation
|
- `Mv.Membership.Import.MemberCSV.validate_row/3` — CSV import (schemaless
|
||||||
- `Mv.Membership.Member` validations - Member resource validation
|
changeset: trims whitespace, requires email, then validates format via the
|
||||||
- `Mv.Accounts.User` validations - User resource validation
|
shared checks).
|
||||||
|
- `Mv.Membership.Member` validations — Member resource.
|
||||||
|
- `Mv.Accounts.User` validations — User resource.
|
||||||
|
|
||||||
All three locations use `Mv.Constants.email_validator_checks()` to ensure consistency.
|
Member and User use similar schemaless changesets inside their Ash validations.
|
||||||
|
|
||||||
## Implementation Details
|
## Changing the validation strategy
|
||||||
|
|
||||||
### CSV Import Validation
|
Update `@email_validator_checks` in `Mv.Constants`; the change applies
|
||||||
|
everywhere automatically.
|
||||||
The CSV import uses a schemaless changeset for email validation:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
changeset =
|
|
||||||
{%{}, %{email: :string}}
|
|
||||||
|> Ecto.Changeset.cast(%{email: Map.get(member_attrs, :email)}, [:email])
|
|
||||||
|> Ecto.Changeset.update_change(:email, &String.trim/1)
|
|
||||||
|> Ecto.Changeset.validate_required([:email])
|
|
||||||
|> EctoCommons.EmailValidator.validate_email(:email, checks: Mv.Constants.email_validator_checks())
|
|
||||||
```
|
|
||||||
|
|
||||||
This approach:
|
|
||||||
- Trims whitespace before validation
|
|
||||||
- Validates email is required
|
|
||||||
- Validates email format using the centralized checks
|
|
||||||
- Provides consistent error messages via Gettext
|
|
||||||
|
|
||||||
### Resource Validations
|
|
||||||
|
|
||||||
Both `Member` and `User` resources use similar schemaless changesets within their Ash validations, ensuring consistent validation behavior across the application.
|
|
||||||
|
|
||||||
## Changing the Validation Strategy
|
|
||||||
|
|
||||||
To change the email validation checks, update the `@email_validator_checks` constant in `Mv.Constants`. This will automatically apply to all validation points.
|
|
||||||
|
|
||||||
**Note:** Changing the validation strategy may affect existing data. Consider:
|
|
||||||
- Whether existing emails will still be valid
|
|
||||||
- Migration strategy for invalid emails
|
|
||||||
- User communication if validation becomes stricter
|
|
||||||
|
|
||||||
|
**Migration caveat:** tightening validation may invalidate existing data.
|
||||||
|
Consider whether stored emails are still valid, a migration strategy for those
|
||||||
|
that are not, and user communication.
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,32 @@
|
||||||
# PDF Generation: Imprintor statt Chromium
|
# PDF Generation: Imprintor instead of Chromium
|
||||||
|
|
||||||
## Übersicht
|
## Decision
|
||||||
|
|
||||||
Für die PDF-Generierung in der Mitgliederverwaltung verwenden wir **Imprintor** (`~> 0.5.0`) anstelle von Chromium-basierten Lösungen (wie z.B. Puppeteer, Chrome Headless, oder ähnliche).
|
For PDF generation we use **Imprintor** (`{:imprintor, "~> 0.6.0"}`) with
|
||||||
|
**Typst** templates, rather than a Chromium-based renderer (Puppeteer, Chrome
|
||||||
|
Headless, etc.). Implemented in `lib/mv/membership/members_pdf.ex`, template at
|
||||||
|
`priv/pdf_templates/members_export.typ`.
|
||||||
|
|
||||||
## Warum Imprintor statt Chromium?
|
## Rationale (Imprintor over Chromium)
|
||||||
|
|
||||||
### 1. Ressourceneffizienz
|
- **Resource efficiency:** no full browser instance in memory, no
|
||||||
|
browser-rendering pipeline on the CPU.
|
||||||
|
- **Smaller Docker images:** no Chromium install (saves several hundred MB);
|
||||||
|
works in minimal images (e.g. Alpine), with no system dependencies
|
||||||
|
(Chromium, ChromeDriver) to ship or keep updated.
|
||||||
|
- **Elixir-native:** integrates with the BEAM and Elixir error handling instead
|
||||||
|
of managing an external browser process; faster generation and easier
|
||||||
|
parallelism (no browser startup or instance management).
|
||||||
|
- **Smaller attack surface:** no browser engine with its own CVE stream.
|
||||||
|
|
||||||
- **Geringerer Speicherverbrauch**: Imprintor benötigt keine vollständige Browser-Instanz im Speicher
|
## When Chromium would still be warranted
|
||||||
- **Niedrigere CPU-Last**: Native PDF-Generierung ohne Browser-Rendering-Pipeline
|
|
||||||
- **Kleinere Docker-Images**: Keine Chromium-Installation erforderlich (spart mehrere hundert MB)
|
|
||||||
|
|
||||||
### 2. Performance
|
A Chromium-based renderer makes sense when the document requires JavaScript
|
||||||
|
execution, dynamic JS-rendered content, modern web CSS features, or full-page
|
||||||
- **Schnellere Generierung**: Direkte PDF-Generierung ohne HTML-Rendering-Overhead
|
screenshots of web pages — none of which apply to our static, template-driven
|
||||||
- **Bessere Skalierbarkeit**: Kann mehrere PDFs parallel generieren ohne Browser-Instanzen zu verwalten
|
exports.
|
||||||
- **Niedrigere Latenz**: Keine Browser-Startup-Zeit
|
|
||||||
|
|
||||||
### 3. Deployment & Wartung
|
|
||||||
|
|
||||||
- **Einfacheres Deployment**: Keine System-Abhängigkeiten (Chromium, ChromeDriver, etc.)
|
|
||||||
- **Weniger Wartungsaufwand**: Keine Browser-Version-Updates zu verwalten
|
|
||||||
- **Bessere Container-Kompatibilität**: Funktioniert in minimalen Docker-Images (z.B. Alpine)
|
|
||||||
|
|
||||||
### 4. Sicherheit
|
|
||||||
|
|
||||||
- **Kleinere Angriffsfläche**: Keine Browser-Engine mit bekannten Sicherheitslücken
|
|
||||||
- **Isolation**: Weniger System-Calls und externe Prozesse
|
|
||||||
|
|
||||||
### 5. Elixir-Native Lösung
|
|
||||||
|
|
||||||
- **Erlang/OTP-Integration**: Nutzt die Vorteile der BEAM-VM (Concurrency, Fault Tolerance)
|
|
||||||
- **Type-Safety**: Bessere Integration mit Elixir-Typen und Pattern Matching
|
|
||||||
- **Einfachere Fehlerbehandlung**: Elixir-native Error-Handling statt externer Prozesse
|
|
||||||
|
|
||||||
## Wann Chromium trotzdem sinnvoll wäre
|
|
||||||
|
|
||||||
Chromium-basierte Lösungen sind sinnvoll, wenn:
|
|
||||||
- Komplexe JavaScript-Ausführung im HTML nötig ist
|
|
||||||
- Moderne CSS-Features (Grid, Flexbox, etc.) kritisch sind
|
|
||||||
- Screenshots von Web-Seiten generiert werden sollen
|
|
||||||
- Dynamische Inhalte gerendert werden müssen, die JavaScript erfordern
|
|
||||||
|
|
||||||
## Verwendung in diesem Projekt
|
|
||||||
|
|
||||||
Imprintor wird für folgende Anwendungsfälle verwendet:
|
|
||||||
- **Member-Export als PDF**: Generierung von Mitgliederlisten und -reports
|
|
||||||
- **Statische Reports**: PDF-Generierung für vordefinierte Report-Formate
|
|
||||||
- **Dokumente**: Generierung von Mitgliedschaftsbescheinigungen, Rechnungen, etc.
|
|
||||||
|
|
||||||
## Technische Details
|
|
||||||
|
|
||||||
- **Dependency**: `{:imprintor, "~> 0.5.0"}`
|
|
||||||
- **Typ**: Native Elixir-Bibliothek (vermutlich basierend auf Rust-NIFs oder ähnlichen Technologien)
|
|
||||||
- **Format**: Generiert PDF direkt aus HTML/Templates ohne Browser-Engine
|
|
||||||
|
|
||||||
## Migration von Chromium (falls vorhanden)
|
|
||||||
|
|
||||||
Falls zuvor eine Chromium-basierte Lösung verwendet wurde:
|
|
||||||
1. HTML-Templates müssen ggf. angepasst werden (kein JavaScript-Support)
|
|
||||||
2. CSS muss statisch sein (keine dynamischen Styles)
|
|
||||||
3. Komplexe Layouts sollten vorher getestet werden
|
|
||||||
|
|
||||||
## Weitere Ressourcen
|
|
||||||
|
|
||||||
- [Imprintor auf Hex.pm](https://hex.pm/packages/imprintor)
|
|
||||||
- [GitHub Repository](https://github.com/[imprintor-repo]) (falls verfügbar)
|
|
||||||
|
|
||||||
|
## Usage in this project
|
||||||
|
|
||||||
|
Member export as PDF (member lists / reports) and other static, predefined
|
||||||
|
documents (e.g. membership certificates).
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,10 @@ Subsections use their own headings (h3) inside the main "Authentication" form_se
|
||||||
| Association Name: [________________] [Save Name] |
|
| Association Name: [________________] [Save Name] |
|
||||||
+------------------------------------------------------------------+
|
+------------------------------------------------------------------+
|
||||||
|
|
||||||
+-- Join Form -----------------------------------------------------+
|
+-- Join Form / SMTP / Accounting-Software Integration ------------+
|
||||||
| ... (unchanged) |
|
| ... (unchanged) |
|
||||||
+------------------------------------------------------------------+
|
+------------------------------------------------------------------+
|
||||||
|
|
||||||
+-- SMTP / E-Mail -------------------------------------------------+
|
|
||||||
| ... |
|
|
||||||
+------------------------------------------------------------------+
|
|
||||||
|
|
||||||
+-- Accounting-Software (Vereinfacht) Integration -----------------+
|
|
||||||
| ... |
|
|
||||||
+------------------------------------------------------------------+
|
|
||||||
|
|
||||||
+-- Authentication ------------------------------------------------+ <-- main section (renamed from "OIDC (Single Sign-On)")
|
+-- Authentication ------------------------------------------------+ <-- main section (renamed from "OIDC (Single Sign-On)")
|
||||||
| |
|
| |
|
||||||
| Direct registration | <-- subsection heading (h3)
|
| Direct registration | <-- subsection heading (h3)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue