1 Commits

Author SHA1 Message Date
mrcwlnd ed36d0e1ec Merge pull request 'WASM Mode activated' (#1) from wasm-integration into main
Reviewed-on: #1
2026-06-08 14:25:30 +00:00
10 changed files with 18 additions and 82 deletions
@@ -29,7 +29,7 @@
<MudNavMenu> <MudNavMenu>
<MudNavLink Href="/changelog" Icon="@Icons.Material.Filled.NewReleases" <MudNavLink Href="/changelog" Icon="@Icons.Material.Filled.NewReleases"
Style="color: var(--mud-palette-text-disabled); font-size:0.75rem;"> Style="color: var(--mud-palette-text-disabled); font-size:0.75rem;">
Version 1.3 Version 1.2
</MudNavLink> </MudNavLink>
</MudNavMenu> </MudNavMenu>
</MudTooltip> </MudTooltip>
@@ -68,17 +68,13 @@
private readonly List<Release> _releases = private readonly List<Release> _releases =
[ [
new("1.3", "09.06.2026", true,
[
new("Fix", "Browser-Tab hat statischen Text angezeigt und sich nicht dynamisch an die jeweilige Seite angepasst"),
]),
new("1.2", "08.06.2026", true, new("1.2", "08.06.2026", true,
[ [
new("Upgrade", "Architektur-Migration auf Hosted Blazor WebAssembly (.NET 10) mit sauberer Projektstruktur (Client, Server, Shared)"), new("Upgrade", "Architektur-Migration auf Hosted Blazor WebAssembly (.NET 10) mit sauberer Projektstruktur (Client, Server, Shared)"),
new("Neu", "Unterstützung für PostgreSQL-Datenbanken als produktive und skalierbare Alternative zu SQLite"), new("Neu", "Unterstützung für PostgreSQL-Datenbanken als produktive und skalierbare Alternative zu SQLite"),
new("Neu", "Dynamische DB-Provider-Weiche (SQLite vs. PostgreSQL) über Konfigurations- und Umgebungsvariablen"), new("Neu", "Dynamische DB-Provider-Weiche (SQLite vs. PostgreSQL) über Konfigurations- und Umgebungsvariablen"),
new("Neu", "Docker-Compose-Konfiguration inklusive PostgreSQL-Container für vereinfachten Deployment-Betrieb"), new("Neu", "Docker-Compose-Konfiguration inklusive PostgreSQL-Container für vereinfachten Deployment-Betrieb"),
new("Neu", "Lokales Ausführungsskript (run-local.sh) für einfaches Testen auf dem Entwicklungsrechner"),
]), ]),
new("1.1", "08.06.2026", false, new("1.1", "08.06.2026", false,
[ [
+4 -23
View File
@@ -358,39 +358,20 @@ else
_userId = int.Parse(claim.Value); _userId = int.Parse(claim.Value);
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today)); _monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
_settings = await TrackerService.GetSettingsAsync(_userId); _settings = await TrackerService.GetSettingsAsync(_userId);
await LoadWeek();
var loadWeekTask = LoadWeek(); _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
var overtimeTask = TrackerService.GetTotalOvertimeAsync(_userId, _settings);
await Task.WhenAll(loadWeekTask, overtimeTask);
_totalOvertime = await overtimeTask;
_loading = false; _loading = false;
} }
private async Task LoadWeek() private async Task LoadWeek()
{ {
Task<List<PublicHoliday>> holidaysTask;
if (_monday.Year != _holidayYear) if (_monday.Year != _holidayYear)
{ {
holidaysTask = HolidayService.GetHolidaysAsync(_monday.Year, _settings.GermanState); var list = await HolidayService.GetHolidaysAsync(_monday.Year, _settings.GermanState);
}
else
{
holidaysTask = Task.FromResult(new List<PublicHoliday>());
}
var dbDaysTask = TrackerService.GetWeekAsync(_userId, _monday);
await Task.WhenAll(holidaysTask, dbDaysTask);
if (_monday.Year != _holidayYear)
{
var list = await holidaysTask;
_holidays = list.ToDictionary(h => h.Date, h => h.Name); _holidays = list.ToDictionary(h => h.Date, h => h.Name);
_holidayYear = _monday.Year; _holidayYear = _monday.Year;
} }
var dbDays = await TrackerService.GetWeekAsync(_userId, _monday);
var dbDays = await dbDaysTask;
_days = Enumerable.Range(0, 7).Select(i => _days = Enumerable.Range(0, 7).Select(i =>
{ {
var date = _monday.AddDays(i); var date = _monday.AddDays(i);
@@ -181,15 +181,9 @@ else
private async Task LoadMonth() private async Task LoadMonth()
{ {
var workDaysTask = TrackerService.GetMonthAsync(_userId, _year, _month); var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
var holidaysTask = HolidayService.GetHolidaysAsync(_year, _settings.GermanState); var holidays = await HolidayService.GetHolidaysAsync(_year, _settings.GermanState);
var vacationsTask = TrackerService.GetVacationDaysAsync(_userId, _year); var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
await Task.WhenAll(workDaysTask, holidaysTask, vacationsTask);
var workDays = await workDaysTask;
var holidays = await holidaysTask;
var vacations = await vacationsTask;
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name); var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
var vacationSet = vacations.Select(v => v.Date).ToHashSet(); var vacationSet = vacations.Select(v => v.Date).ToHashSet();
@@ -495,12 +495,8 @@ else
if (claim == null) return; if (claim == null) return;
_userId = int.Parse(claim.Value); _userId = int.Parse(claim.Value);
_settings = await TrackerService.GetSettingsAsync(_userId); _settings = await TrackerService.GetSettingsAsync(_userId);
await LoadVacations();
var loadVacationsTask = LoadVacations(); _holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
var loadHolidaysTask = HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
await Task.WhenAll(loadVacationsTask, loadHolidaysTask);
_holHolidays = await loadHolidaysTask;
} }
private async Task LoadVacations() private async Task LoadVacations()
@@ -249,13 +249,8 @@ else
private async Task LoadYear() private async Task LoadYear()
{ {
var holidaysTask = HolidayService.GetHolidaysAsync(_year, _settings.GermanState); var holidays = await HolidayService.GetHolidaysAsync(_year, _settings.GermanState);
var vacationsTask = TrackerService.GetVacationDaysAsync(_userId, _year); var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
await Task.WhenAll(holidaysTask, vacationsTask);
var holidays = await holidaysTask;
var vacations = await vacationsTask;
_holidays = holidays.ToDictionary(h => h.Date, h => h.Name); _holidays = holidays.ToDictionary(h => h.Date, h => h.Name);
_vacationSet = vacations.Select(v => v.Date).ToHashSet(); _vacationSet = vacations.Select(v => v.Date).ToHashSet();
_remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count); _remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count);
@@ -6,7 +6,6 @@ namespace timetracker.Client.Services;
public class ClientHolidayService : IHolidayService public class ClientHolidayService : IHolidayService
{ {
private readonly HttpClient _http; private readonly HttpClient _http;
private readonly Dictionary<(int Year, string StateCode), List<PublicHoliday>> _holidayCache = new();
public ClientHolidayService(HttpClient http) public ClientHolidayService(HttpClient http)
{ {
@@ -15,21 +14,12 @@ public class ClientHolidayService : IHolidayService
public async Task<List<PublicHoliday>> GetHolidaysAsync(int year, string? stateCode = null) public async Task<List<PublicHoliday>> GetHolidaysAsync(int year, string? stateCode = null)
{ {
var sc = stateCode ?? "";
var key = (year, sc);
if (_holidayCache.TryGetValue(key, out var cached))
{
return cached;
}
var url = $"api/holidays?year={year}"; var url = $"api/holidays?year={year}";
if (!string.IsNullOrEmpty(stateCode)) if (!string.IsNullOrEmpty(stateCode))
{ {
url += $"&stateCode={stateCode}"; url += $"&stateCode={stateCode}";
} }
var result = await _http.GetFromJsonAsync<List<PublicHoliday>>(url) ?? []; return await _http.GetFromJsonAsync<List<PublicHoliday>>(url) ?? [];
_holidayCache[key] = result;
return result;
} }
public async Task<(bool Success, string Message)> FetchAndStoreAsync(int year) public async Task<(bool Success, string Message)> FetchAndStoreAsync(int year)
@@ -40,12 +30,6 @@ public class ClientHolidayService : IHolidayService
var result = await response.Content.ReadFromJsonAsync<FetchResponse>(); var result = await response.Content.ReadFromJsonAsync<FetchResponse>();
if (result != null) if (result != null)
{ {
// Invalidate the cache for this year
var keysToRemove = _holidayCache.Keys.Where(k => k.Year == year).ToList();
foreach (var k in keysToRemove)
{
_holidayCache.Remove(k);
}
return (result.Success, result.Message); return (result.Success, result.Message);
} }
} }
@@ -55,8 +39,6 @@ public class ClientHolidayService : IHolidayService
public async Task DeleteAsync(int id) public async Task DeleteAsync(int id)
{ {
await _http.DeleteAsync($"api/holidays/{id}"); await _http.DeleteAsync($"api/holidays/{id}");
// Invalidate all caches since we deleted a holiday
_holidayCache.Clear();
} }
private class FetchResponse private class FetchResponse
@@ -6,7 +6,6 @@ namespace timetracker.Client.Services;
public class ClientTimetrackerService : ITimetrackerService public class ClientTimetrackerService : ITimetrackerService
{ {
private readonly HttpClient _http; private readonly HttpClient _http;
private AppSettings? _cachedSettings;
public ClientTimetrackerService(HttpClient http) public ClientTimetrackerService(HttpClient http)
{ {
@@ -25,19 +24,12 @@ public class ClientTimetrackerService : ITimetrackerService
public async Task<AppSettings> GetSettingsAsync(int userId) public async Task<AppSettings> GetSettingsAsync(int userId)
{ {
if (_cachedSettings != null && _cachedSettings.UserId == userId) return await _http.GetFromJsonAsync<AppSettings>($"api/tracker/settings/{userId}") ?? new AppSettings { UserId = userId };
{
return _cachedSettings;
}
var settings = await _http.GetFromJsonAsync<AppSettings>($"api/tracker/settings/{userId}");
_cachedSettings = settings ?? new AppSettings { UserId = userId };
return _cachedSettings;
} }
public async Task SaveSettingsAsync(AppSettings settings) public async Task SaveSettingsAsync(AppSettings settings)
{ {
await _http.PostAsJsonAsync("api/tracker/settings", settings); await _http.PostAsJsonAsync("api/tracker/settings", settings);
_cachedSettings = settings;
} }
public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year) public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
+1 -1
View File
@@ -13,7 +13,7 @@
<ImportMap /> <ImportMap />
<link rel="icon" type="image/svg+xml" href="favicon.svg" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="alternate icon" type="image/png" href="favicon.png" /> <link rel="alternate icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="InteractiveWebAssembly" /> <HeadOutlet />
</head> </head>
<body> <body>
+2 -2
View File
@@ -3,13 +3,13 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src WORKDIR /src
# Copy project files for restoring dependencies # Copy project files for restoring dependencies
COPY timetracker.slnx ./ COPY timetracker.sln ./
COPY timetracker.Server/timetracker.Server.csproj timetracker.Server/ COPY timetracker.Server/timetracker.Server.csproj timetracker.Server/
COPY timetracker.Client/timetracker.Client.csproj timetracker.Client/ COPY timetracker.Client/timetracker.Client.csproj timetracker.Client/
COPY timetracker.Shared/timetracker.Shared.csproj timetracker.Shared/ COPY timetracker.Shared/timetracker.Shared.csproj timetracker.Shared/
# Restore dependencies # Restore dependencies
RUN dotnet restore timetracker.slnx RUN dotnet restore timetracker.sln
# Copy the rest of the source code # Copy the rest of the source code
COPY timetracker.Server/ timetracker.Server/ COPY timetracker.Server/ timetracker.Server/