Add customer logo area to action bar
Introduced a customer logo section in `EnvelopeSenderPage.razor` that dynamically displays a customer logo or a placeholder when no logo is configured. The logo is fetched from the `CustomImagesService` and styled using new CSS classes in `sender-page.css`. - Added `_companyLogo` property to store customer logo configuration. - Updated `CustomImagesService` to fetch both app and customer logos. - Modified `appsettings.json` to include logo paths and styles. - Enhanced CSS for a three-section action bar layout. - Added placeholder styling for missing customer logos. - Updated initialization logic to load logos dynamically. - Documented implementation details in `SF-74-PLAN.md`. The implementation is modular and deployment-specific, allowing logo paths and styles to be updated via configuration without code changes.
This commit is contained in:
@@ -41,6 +41,22 @@
|
||||
<div class="sender-title">Umschlag-Übersicht</div>
|
||||
</div>
|
||||
|
||||
@* ── Kunden-Logo (Mitte) ───────────────────────────────────────── *@
|
||||
<div class="sender-customer-logo-area">
|
||||
@if (_companyLogo is not null && !string.IsNullOrEmpty(_companyLogo.Src))
|
||||
{
|
||||
<img src="@_companyLogo.Src"
|
||||
style="height: @_companyLogo.Height"
|
||||
class="@_companyLogo.GetClass("Show")"
|
||||
alt="Kunden-Logo" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-customer-logo-placeholder">Kunden-Logo</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ── Toolbar (rechts) ─────────────────────────────────────────── *@
|
||||
<div class="sender-toolbar">
|
||||
<button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="Neuen Umschlag erstellen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
@@ -355,6 +371,7 @@
|
||||
|
||||
// Logo config loaded from /api/CustomImages after auth
|
||||
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
||||
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _companyLogo;
|
||||
|
||||
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
|
||||
private const string GridKeyActive = "sender.active-envelopes";
|
||||
@@ -377,7 +394,8 @@
|
||||
|
||||
// Load logo config — authenticated, so this should succeed
|
||||
var logos = await CustomImagesService.GetAsync();
|
||||
_appLogo = logos.App;
|
||||
_appLogo = logos.App;
|
||||
_companyLogo = logos.Company;
|
||||
|
||||
await LoadEnvelopesAsync();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,34 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0; /* don't shrink — logo area in the middle gets the space */
|
||||
}
|
||||
|
||||
/* ── Customer logo area (middle of action bar) ──────────────────────────── */
|
||||
.sender-customer-logo-area {
|
||||
flex: 1; /* takes up remaining space between left and right */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 0; /* allow flex child to shrink below content size */
|
||||
}
|
||||
|
||||
/* Placeholder shown when no customer logo is configured */
|
||||
.sf-customer-logo-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
min-width: 90px;
|
||||
padding: 0 12px;
|
||||
border: 1px dashed rgba(126, 34, 206, 0.28);
|
||||
border-radius: 6px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
color: rgba(126, 34, 206, 0.38);
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sender-logo svg {
|
||||
|
||||
415
EnvelopeGenerator.Server/SF-74-PLAN.md
Normal file
415
EnvelopeGenerator.Server/SF-74-PLAN.md
Normal file
@@ -0,0 +1,415 @@
|
||||
# SF-74 — signFLOW: Theme, Dark Mode, Grid Persistence, Logo & Dashboard
|
||||
|
||||
**Ticket:** SF-74 | **Bearbeiter:** Hakan Tek | **Status:** In Bearbeitung
|
||||
**Letzte Aktualisierung:** 2026-09-03
|
||||
**Erstellt von:** Marlon Schreiber | **Verknüpft mit:** SF-71
|
||||
|
||||
---
|
||||
|
||||
## Originale Anforderungen
|
||||
|
||||
### Marlon Schreiber (Ticket-Text):
|
||||
1. **signFLOW-Logo** + **Kunden-Logo-Bereich** auf der Startseite
|
||||
2. **DevExpress Skin-Auswahl** — mindestens 3 Themes, vom User wählbar + gespeichert
|
||||
3. **Grid-Layout-Persistenz** — Spaltenbreite/Position pro User gespeichert
|
||||
|
||||
### Screenshot-Annotationen (`/sender`):
|
||||
```
|
||||
[signFLOW Logo] Umschlag-Übersicht [Kunden-Logo] [+Neuer Umschlag][Bearb.][Lösch.][↺][→|]
|
||||
signFLOW Portal-Übersicht [Zurückrufen/löschen][Dok.anzeigen][Excel]
|
||||
|
||||
[Info-Banner: "Die Größe und Position der Spalten muss verändert und für den User gespeichert..."]
|
||||
|
||||
ID ↓ Titel Status [Typ] Empfänger [Erstellt] [Geändert am]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projektstruktur — Clean Architecture
|
||||
|
||||
```
|
||||
EnvelopeGenerator.Domain/ → Entities, Domain-Interfaces
|
||||
EnvelopeGenerator.Application/ → Service-Interfaces, DTOs, Use-Cases
|
||||
EnvelopeGenerator.Infrastructure/ → Cache, DB, Implementierungen
|
||||
EnvelopeGenerator.Server/ → Blazor SSR Host, Controller, Middleware
|
||||
EnvelopeGenerator.Server.Client/ → Blazor WASM Client
|
||||
```
|
||||
|
||||
**Abhängigkeitsregel:** `Domain ← Application ← Infrastructure ← Server / Server.Client`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Abgeschlossene Phasen (1–5)
|
||||
|
||||
| Phase | Inhalt | Status |
|
||||
|-------|--------|--------|
|
||||
| 1 | Server: UserPreferences API (TBDD_CACHE, `IUserPreferencesService`) | ✅ |
|
||||
| 2 | WASM: `ThemeService`, `UserPreferencesService` (1h In-Memory-Cache) | ✅ |
|
||||
| 3 | Footer Theme Slider (🌙 blazing-dark ── blazing-berry ── purple ☀️) | ✅ |
|
||||
| 4 | Grid Layout Persistence (`LayoutAutoSaving`/`LayoutAutoLoading`) | ✅ |
|
||||
| 5 | Theme-CSS-Overrides für alle Seiten (`[data-sf-theme]`) | ✅ |
|
||||
|
||||
**Key decisions:** TBDD_CACHE statt eigener DB-Tabelle · `SenderOrReceiver` Policy · Slider statt Toggle · `signflow.theme` in localStorage · DevExpress 25.2.3 hat keine `.dark.`-Suffix-Dateien
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6a — signFLOW-Logo
|
||||
|
||||
### Ziel
|
||||
signFLOW-App-Logo im Sender Dashboard (`/sender`) Action Bar links neben dem Titel, sowie auf der Startseite (`/`) im Hero-Header.
|
||||
|
||||
### Technischer Ansatz
|
||||
|
||||
**Options Pattern** — `appsettings.json` → `CustomImagesOptions` → DI → Controller → WASM Service.
|
||||
|
||||
```
|
||||
appsettings.json
|
||||
└── CustomImagesOptions (Options Pattern, IOptions<>)
|
||||
└── GET /api/CustomImages (neuer Controller, auth: SenderOrReceiver)
|
||||
└── CustomImagesService (WASM, in-memory cache, kein TTL)
|
||||
└── EnvelopeSenderPage + IndexPage
|
||||
```
|
||||
|
||||
**Warum kein `wwwroot/appsettings.json`?**
|
||||
Statische Assets sind public (keine Auth). Logo-Pfade sind deployment-spezifisch.
|
||||
Options Pattern erlaubt Produktionswechsel ohne Code-Änderung.
|
||||
|
||||
### Neue/geänderte Dateien
|
||||
|
||||
#### `Server/Models/CustomImages.cs` — NEU
|
||||
|
||||
```csharp
|
||||
public sealed class CustomImageEntry
|
||||
{
|
||||
public string Src { get; set; } = string.Empty;
|
||||
/// <summary>CSS height, e.g. "28px" or "2rem". Change per deployment in appsettings.</summary>
|
||||
public string Height { get; set; } = "28px";
|
||||
public Dictionary<string, string> Classes { get; set; } = new();
|
||||
public string GetClass(string key) =>
|
||||
Classes.TryGetValue(key, out var cls) ? cls ?? string.Empty : string.Empty;
|
||||
}
|
||||
|
||||
public sealed class CustomImagesOptions
|
||||
{
|
||||
public const string SectionName = "CustomImages";
|
||||
public CustomImageEntry App { get; set; } = new();
|
||||
public CustomImageEntry Company { get; set; } = new();
|
||||
}
|
||||
```
|
||||
|
||||
#### `appsettings.json` — Änderung (Zeile 145)
|
||||
|
||||
```json
|
||||
"CustomImages": {
|
||||
"App": { "Src": "/img/DD_signFLOW_LOGO.png", "Height": "28px",
|
||||
"Classes": { "Main": "signflow-app-logo" } },
|
||||
"Company": { "Src": "/img/digital_data.svg", "Height": "22px",
|
||||
"Classes": { "Show": "signflow-company-logo" } }
|
||||
}
|
||||
```
|
||||
|
||||
> **Deployment:** Nur `Src` + `Height` in `appsettings.json` ändern — keine Code-Anpassung nötig.
|
||||
|
||||
#### `Server/Controllers/CustomImagesController.cs` — NEU
|
||||
|
||||
```
|
||||
GET /api/CustomImages
|
||||
Auth: [Authorize(Policy = AuthPolicy.SenderOrReceiver)]
|
||||
Returns: CustomImagesOptions
|
||||
```
|
||||
|
||||
#### `Server/Program.cs` — DI
|
||||
|
||||
```csharp
|
||||
builder.Services.Configure<CustomImagesOptions>(
|
||||
config.GetSection(CustomImagesOptions.SectionName));
|
||||
```
|
||||
|
||||
#### `Server/wwwroot/img/` — Neue Dateien
|
||||
|
||||
Kopiert aus `EnvelopeGenerator.Web/wwwroot/img/`:
|
||||
- `DD_signFLOW_LOGO.png`
|
||||
- `digital_data.svg`
|
||||
|
||||
#### `Client/Services/CustomImagesService.cs` — NEU
|
||||
|
||||
```csharp
|
||||
// GET /api/CustomImages → einmalig laden, in-memory gecacht (Config ändert sich kaum)
|
||||
public class CustomImagesService(IHttpClientFactory factory)
|
||||
{
|
||||
private CustomImagesDto? _cached;
|
||||
public async Task<CustomImagesDto> GetAsync(CancellationToken ct = default) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
#### `app.css` — Neue Klassen
|
||||
|
||||
```css
|
||||
.signflow-app-logo { height: var(--sf-app-height, 28px); width: auto; object-fit: contain; }
|
||||
.signflow-company-logo { height: var(--sf-company-height, 22px); width: auto; object-fit: contain; opacity: .88; }
|
||||
```
|
||||
|
||||
#### `IndexPage.razor` — Änderung
|
||||
|
||||
```razor
|
||||
<!-- Alt: <svg class="home-hero-header__icon"> -->
|
||||
<!-- Neu: -->
|
||||
<img src="@_appLogo.Src" style="height: @_appLogo.Height"
|
||||
class="home-hero-header__icon" alt="signFLOW" />
|
||||
```
|
||||
|
||||
#### `EnvelopeSenderPage.razor` — signFLOW Logo im Action Bar
|
||||
|
||||
```razor
|
||||
<div class="sender-branding">
|
||||
<img src="@_appLogo.Src" style="height: @_appLogo.Height"
|
||||
class="@_appLogo.GetClass("Main")" alt="signFLOW" />
|
||||
<div class="sender-title-block">
|
||||
<div class="sender-title">Umschlag-Übersicht</div>
|
||||
<div class="sender-subtitle">signFLOW Portal - Übersicht</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6b — Kunden-Logo Placeholder
|
||||
|
||||
### Ziel
|
||||
Mittlerer Bereich im Action Bar zeigt das Kunden-Logo (aus Config) oder einen Platzhalter.
|
||||
|
||||
### Implementierung
|
||||
|
||||
**Config-Quelle:** `CustomImagesOptions.Company` (aus Phase 6a bereits geladen)
|
||||
|
||||
```razor
|
||||
<!-- Mitte des Action Bars -->
|
||||
<div class="sender-customer-logo-area">
|
||||
@if (!string.IsNullOrEmpty(_companyLogo?.Src))
|
||||
{
|
||||
<img src="@_companyLogo.Src" style="height: @_companyLogo.Height"
|
||||
class="@_companyLogo.GetClass("Show")" alt="Kunden-Logo" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-customer-logo-placeholder">Kunden-Logo</div>
|
||||
}
|
||||
</div>
|
||||
```
|
||||
|
||||
#### `app.css` — Neue Klasse
|
||||
|
||||
```css
|
||||
.sf-customer-logo-placeholder {
|
||||
height: 24px; min-width: 90px;
|
||||
border: 1px dashed rgba(126, 34, 206, 0.3); border-radius: 4px;
|
||||
display: flex; align-items: center; padding: 0 10px;
|
||||
font-size: 0.62rem; color: rgba(126, 34, 206, 0.4); white-space: nowrap;
|
||||
}
|
||||
```
|
||||
|
||||
#### `sender-page.css` — Neues Layout (3-Bereich-Action-Bar)
|
||||
|
||||
```css
|
||||
.sender-branding { display: flex; align-items: center; gap: .75rem; flex-shrink: 0; }
|
||||
.sender-subtitle { font-size: .72rem; color: #6b7280; font-weight: 400; }
|
||||
.sender-customer-logo-area { flex: 1; display: flex; justify-content: center; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6c — Grid: Fehlende Spalten + Persistence Hint
|
||||
|
||||
### Fehlende Spalten (aus Screenshot-Annotationen)
|
||||
|
||||
**Verfügbarkeit in `EnvelopeDto`:**
|
||||
- `EnvelopeTypeTitle` (string?) → `spalte für "Typ"` ✅
|
||||
- `AddedWhen` (DateTime) → `spalte für "erstellt"` ✅
|
||||
- `ChangedWhen` (DateTime?) → `Spalte "zuletzt geändert am"` ✅
|
||||
|
||||
> ⚠️ **Vor Implementierung prüfen:** `EnvelopeTypeTitle` wird aus dem Navigation Property `EnvelopeType` gemappt. Mapping-Konfiguration in `Application/...MappingProfile.cs` verifizieren.
|
||||
|
||||
#### `EnvelopeSenderPage.razor` — Neue Spalten (beide Grids)
|
||||
|
||||
```razor
|
||||
<DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="Typ"
|
||||
Width="120px" AllowSort="true" />
|
||||
<DxGridDataColumn FieldName="AddedWhen" Caption="Erstellt"
|
||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
||||
<DxGridDataColumn FieldName="ChangedWhen" Caption="Geändert am"
|
||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
||||
```
|
||||
|
||||
### Grid Persistence Hint
|
||||
|
||||
Info-Banner über dem Grid:
|
||||
|
||||
```razor
|
||||
<div class="sf-grid-persistence-hint">
|
||||
<svg><!-- info icon --></svg>
|
||||
Die Größe und Position der Spalten wird automatisch für Sie gespeichert.
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.sf-grid-persistence-hint {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 5px 16px; font-size: 0.72rem;
|
||||
color: rgba(126, 34, 206, 0.7);
|
||||
background: rgba(126, 34, 206, 0.04);
|
||||
border-bottom: 1px solid rgba(126, 34, 206, 0.08);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6d — Dokument anzeigen (PDF Preview)
|
||||
|
||||
### Anforderung
|
||||
Ausgewähltes Envelope-Dokument im Popup anzeigen. Envelope ist nach dem Senden **read-only** (Mails wurden gesendet — nachträgliche Änderungen würden Konsistenz brechen).
|
||||
|
||||
### Technischer Ansatz
|
||||
|
||||
`DxPdfViewer` erfordert **zwingend `@rendermode InteractiveServer`**.
|
||||
`EnvelopeSenderPage` ist WASM (`@rendermode InteractiveWebAssembly`).
|
||||
|
||||
**Lösung:** `DxPopup` im WASM-Parent mit einem Server-Component darin:
|
||||
|
||||
```
|
||||
EnvelopeSenderPage.razor (@rendermode WASM)
|
||||
└── <DxPopup>
|
||||
└── <EnvelopePdfPreviewComponent @rendermode InteractiveServer>
|
||||
└── <DxPdfViewer> (SSR-only)
|
||||
```
|
||||
|
||||
#### `Server/Components/Shared/EnvelopePdfPreviewComponent.razor` — NEU
|
||||
|
||||
```razor
|
||||
@rendermode InteractiveServer
|
||||
@inject IDocumentService DocumentService <!-- oder HTTP client -->
|
||||
|
||||
<DxPdfViewer DocumentContent="@_pdfBytes" />
|
||||
|
||||
@code {
|
||||
[Parameter] public int EnvelopeId { get; set; }
|
||||
byte[]? _pdfBytes;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (EnvelopeId > 0)
|
||||
_pdfBytes = await DocumentService.GetPdfBytesAsync(EnvelopeId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Button: nur aktiv wenn Envelope ausgewählt UND Dokument vorhanden (`envelope.Documents?.Any() == true`).
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6e — Export nach Excel
|
||||
|
||||
### Technischer Ansatz
|
||||
|
||||
`DxGrid.ExportToXlsxAsync(fileName, options)` — direkt im Razor-Code, keine Backend-Implementierung nötig.
|
||||
Download geht direkt an den Browser.
|
||||
|
||||
```csharp
|
||||
async Task ExportToExcelAsync()
|
||||
{
|
||||
var grid = _activeTab == "active" ? _gridActive : _gridCompleted;
|
||||
if (grid is null) return;
|
||||
var fileName = $"Umschlaege_{DateTime.Now:yyyy-MM-dd}.xlsx";
|
||||
await grid.ExportToXlsxAsync(fileName, new GridXlExportOptions());
|
||||
}
|
||||
```
|
||||
|
||||
> **Hinweis:** `CellDisplayTemplate`-Inhalte werden nicht exportiert (DevExpress-Limitierung).
|
||||
> Die Spalten `Status`, `EnvelopeReceivers` nutzen Templates → Wert im Export ist leer.
|
||||
> **Future Task:** Für vollständigen Export mit formatierten Zellen `CustomizeCell`-Event nutzen.
|
||||
|
||||
Button in Toolbar:
|
||||
```razor
|
||||
<button class="sender-btn" @onclick="ExportToExcelAsync"
|
||||
title="Aktuelle Ansicht als Excel exportieren">
|
||||
<!-- Excel-Icon SVG -->
|
||||
Export nach Excel
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔲 Phase 6f — Action Bar Redesign
|
||||
|
||||
### Ziel-Layout (aus Screenshot)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ [Logo] Umschlag-Übersicht │ [Kunden-Logo] │ [+Neu] [Bearb.] [Lösch.] │
|
||||
│ signFLOW Portal-Übers│ │ [↺] [→|] │
|
||||
│ │ │ [Zurückrufen?] [Dok.] [XLS] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Neue CSS-Klassen in `sender-page.css`
|
||||
|
||||
```css
|
||||
.sender-toolbar-right { display: flex; flex-direction: column; gap: .35rem; align-items: flex-end; }
|
||||
.sender-toolbar-row { display: flex; align-items: center; gap: .5rem; }
|
||||
.sender-toolbar-row--primary { /* Hauptbuttons */ }
|
||||
.sender-toolbar-row--secondary { /* Dokument anzeigen, Export */ }
|
||||
```
|
||||
|
||||
### `Zurückrufen/löschen` — Offene Frage ❓
|
||||
|
||||
> **Ticket-Kommentar-Entwurf (Deutsch, für Marlon Schreiber):**
|
||||
>
|
||||
> Hallo @Marlon Schreiber,
|
||||
>
|
||||
> im Screenshot sehe ich einen Button „Zurückrufen/löschen". Könntest du den
|
||||
> Unterschied zum bereits vorhandenen „Löschen"-Button erläutern?
|
||||
>
|
||||
> Meine Vermutung: „Zurückrufen" bedeutet das Widerrufen eines **bereits gesendeten**
|
||||
> Umschlags (Status → `EnvelopeWithdrawn`), während „Löschen" einen noch nicht
|
||||
> gesendeten Umschlag entfernt. Ist das korrekt? Und falls ja:
|
||||
> - Sollen Empfänger bei einem Widerruf automatisch benachrichtigt werden?
|
||||
> - Soll der Status auf `Zurückgerufen` gesetzt werden und der Umschlag
|
||||
> weiterhin in der Liste erscheinen (aber inaktiv)?
|
||||
>
|
||||
> Bitte kurz antworten, damit ich dies korrekt implementieren kann.
|
||||
|
||||
---
|
||||
|
||||
## Implementierungsreihenfolge Phase 6
|
||||
|
||||
```
|
||||
6a (Logo) → 6b (Kunden-Logo) → 6c (Grid-Spalten) → 6d (PDF Preview) → 6e (Excel) → 6f (Action Bar)
|
||||
```
|
||||
|
||||
Jede Teilphase ist unabhängig deploybar. `6f` fasst alle Layout-Änderungen zusammen
|
||||
und sollte zuletzt implementiert werden, wenn alle Komponenten fertig sind.
|
||||
|
||||
---
|
||||
|
||||
## Bekannte Einschränkungen & Offene Punkte
|
||||
|
||||
| # | Thema | Status |
|
||||
|---|-------|--------|
|
||||
| 1 | `DxPdfViewer` → InteractiveServer zwingend | Architektonisch gelöst (6d) |
|
||||
| 2 | Excel-Export: Template-Spalten leer im Export | Akzeptiert, Future Task (CustomizeCell) |
|
||||
| 3 | `Zurückrufen/löschen` — Bedeutung unklar | Kommentar-Frage vorbereitet (6f) |
|
||||
| 4 | `EnvelopeTypeTitle` Mapping prüfen | Vor 6c verifizieren |
|
||||
| 5 | Logo-Pfade produktionsspezifisch | Via `appsettings.json` konfigurierbar |
|
||||
| 6 | Server-seitige Pages — inline Farben bleiben | Out-of-scope |
|
||||
|
||||
---
|
||||
|
||||
## Nicht in diesem Ticket
|
||||
|
||||
- `Einstellungen`-Seite (separates Ticket)
|
||||
- Mobile Responsive (separates Ticket)
|
||||
- Excel CustomizeCell vollständige Implementierung (Future Task)
|
||||
- Widerruf-Backend (wartet auf Klärung)
|
||||
|
||||
---
|
||||
|
||||
*Erstellt: 2026-09-01 | Aktualisiert: 2026-09-03 | Ticket: SF-74 | Projekt: signFLOW*
|
||||
Reference in New Issue
Block a user