Add club settings and use club name in PDF exports

This commit is contained in:
2026-03-31 18:05:45 +02:00
parent 27389bf860
commit 68e4e1aa4b
6 changed files with 126 additions and 5 deletions

View File

@@ -4,9 +4,14 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Übersicht</h2> <h2>Übersicht</h2>
<button class="btn btn-primary" @onclick="() => showAddAccount = true"> <div class="d-flex gap-2">
<i class="bi bi-plus-lg"></i> Neues Konto <button class="btn btn-primary" @onclick="() => showAddAccount = true">
</button> <i class="bi bi-plus-lg"></i> Neues Konto
</button>
<a class="btn btn-outline-secondary" href="/settings">
<i class="bi bi-gear"></i> Einstellungen
</a>
</div>
</div> </div>
@if (accounts == null) @if (accounts == null)

View File

@@ -0,0 +1,50 @@
@page "/settings"
@inject ISettingsService SettingsService
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Einstellungen</h2>
<a class="btn btn-outline-secondary" href="/">
<i class="bi bi-arrow-left"></i> Zurück
</a>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3">Verein</h5>
<div class="mb-3">
<label class="form-label">Name des Vereins</label>
<input type="text" class="form-control" @bind="clubName" @bind:event="oninput"
placeholder="Mein Verein" />
<div class="form-text">Wird im PDF-Auszug als Kopfzeile verwendet.</div>
</div>
<button class="btn btn-primary" @onclick="Save" disabled="@saving">
<i class="bi bi-check-lg"></i> Speichern
</button>
@if (saved)
{
<span class="text-success ms-2"><i class="bi bi-check-circle"></i> Gespeichert</span>
}
</div>
</div>
</div>
@code {
private string clubName = string.Empty;
private bool saving;
private bool saved;
protected override async Task OnInitializedAsync()
{
clubName = await SettingsService.GetClubNameAsync() ?? string.Empty;
}
private async Task Save()
{
saving = true;
saved = false;
await SettingsService.SetClubNameAsync(clubName);
saved = true;
saving = false;
}
}

View File

@@ -0,0 +1,7 @@
namespace Duempelkas.App.Services;
public interface ISettingsService
{
Task<string?> GetClubNameAsync();
Task SetClubNameAsync(string? clubName);
}

View File

@@ -18,6 +18,7 @@ public static class DependencyInjection
services.AddScoped<IBalanceQueryService, BalanceQueryService>(); services.AddScoped<IBalanceQueryService, BalanceQueryService>();
services.AddScoped<IPdfStatementService, PdfStatementService>(); services.AddScoped<IPdfStatementService, PdfStatementService>();
services.AddScoped<IFileSaveService, FileSaveService>(); services.AddScoped<IFileSaveService, FileSaveService>();
services.AddSingleton<ISettingsService, SettingsService>();
return services; return services;
} }

View File

@@ -15,13 +15,15 @@ public class PdfStatementService : IPdfStatementService
private readonly FinanceDbContext _db; private readonly FinanceDbContext _db;
private readonly IEntryService _entryService; private readonly IEntryService _entryService;
private readonly IBalanceQueryService _balanceQueryService; private readonly IBalanceQueryService _balanceQueryService;
private readonly ISettingsService _settingsService;
private static readonly CultureInfo DeLocale = new("de-DE"); private static readonly CultureInfo DeLocale = new("de-DE");
public PdfStatementService(FinanceDbContext db, IEntryService entryService, IBalanceQueryService balanceQueryService) public PdfStatementService(FinanceDbContext db, IEntryService entryService, IBalanceQueryService balanceQueryService, ISettingsService settingsService)
{ {
_db = db; _db = db;
_entryService = entryService; _entryService = entryService;
_balanceQueryService = balanceQueryService; _balanceQueryService = balanceQueryService;
_settingsService = settingsService;
} }
public async Task<byte[]> GenerateStatementAsync(int accountId, bool currentYearOnly) public async Task<byte[]> GenerateStatementAsync(int accountId, bool currentYearOnly)
@@ -31,6 +33,7 @@ public class PdfStatementService : IPdfStatementService
var entries = await _entryService.GetEntriesAsync(accountId, currentYearOnly); var entries = await _entryService.GetEntriesAsync(accountId, currentYearOnly);
var balance = await _balanceQueryService.GetAccountBalanceAsync(accountId); var balance = await _balanceQueryService.GetAccountBalanceAsync(accountId);
var clubName = await _settingsService.GetClubNameAsync() ?? "Mein Verein";
var title = currentYearOnly var title = currentYearOnly
? $"{account.Name} Auszug {DateTime.Now.Year}" ? $"{account.Name} Auszug {DateTime.Now.Year}"
@@ -47,7 +50,7 @@ public class PdfStatementService : IPdfStatementService
page.Header().Column(col => page.Header().Column(col =>
{ {
col.Item().Text(account.Name).Bold().FontSize(20); col.Item().Text(clubName).Bold().FontSize(20);
col.Item().Text(title).FontSize(14).FontColor(Colors.Grey.Darken1); col.Item().Text(title).FontSize(14).FontColor(Colors.Grey.Darken1);
col.Item().PaddingTop(5).Text($"Erstellt am: {DateTime.Now:dd.MM.yyyy}").FontSize(8).FontColor(Colors.Grey.Medium); col.Item().PaddingTop(5).Text($"Erstellt am: {DateTime.Now:dd.MM.yyyy}").FontSize(8).FontColor(Colors.Grey.Medium);
col.Item().PaddingTop(10).LineHorizontal(1).LineColor(Colors.Grey.Lighten2); col.Item().PaddingTop(10).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);

View File

@@ -0,0 +1,55 @@
using System.Text.Json;
using Duempelkas.App.Services;
namespace Duempelkas.Infrastructure.Services;
public class SettingsService : ISettingsService
{
private static readonly string SettingsPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Duempelkas", "settings.json");
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
public async Task<string?> GetClubNameAsync()
{
var settings = await LoadAsync();
return settings.ClubName;
}
public async Task SetClubNameAsync(string? clubName)
{
var settings = await LoadAsync();
settings.ClubName = string.IsNullOrWhiteSpace(clubName) ? null : clubName.Trim();
await SaveAsync(settings);
}
private static async Task<AppSettings> LoadAsync()
{
if (!File.Exists(SettingsPath))
return new AppSettings();
try
{
var json = await File.ReadAllTextAsync(SettingsPath);
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
}
catch
{
return new AppSettings();
}
}
private static async Task SaveAsync(AppSettings settings)
{
var dir = Path.GetDirectoryName(SettingsPath)!;
Directory.CreateDirectory(dir);
var json = JsonSerializer.Serialize(settings, JsonOptions);
await File.WriteAllTextAsync(SettingsPath, json);
}
private class AppSettings
{
public string? ClubName { get; set; }
}
}