Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f262910ad | |||
| ed36d0e1ec |
@@ -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,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user