Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f6d070e6b | |||
| 7ab824e7c1 |
@@ -6,5 +6,15 @@
|
|||||||
<MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
|
<MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
|
||||||
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
||||||
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
||||||
|
<MudDivider Class="mt-2 mb-2" />
|
||||||
|
<AuthorizeView>
|
||||||
|
<Authorized>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-1" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Color="Color.Primary" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.body2" Style="font-weight:600">@context.User.Identity?.Name</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
|
||||||
|
</Authorized>
|
||||||
|
</AuthorizeView>
|
||||||
</MudNavMenu>
|
</MudNavMenu>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/feiertage"
|
@page "/feiertage"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
@attribute [Authorize]
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
|
|
||||||
<PageTitle>Feiertage – Timetracker</PageTitle>
|
<PageTitle>Feiertage – Timetracker</PageTitle>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
|
|
||||||
<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>
|
<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -332,6 +334,7 @@ else
|
|||||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||||
|
|
||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
|
private int _userId;
|
||||||
private DateOnly _monday;
|
private DateOnly _monday;
|
||||||
private List<DayVm> _days = [];
|
private List<DayVm> _days = [];
|
||||||
private AppSettings _settings = new();
|
private AppSettings _settings = new();
|
||||||
@@ -349,10 +352,14 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
|
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||||
|
if (claim == null) return; // Prerender-Pass – Circuit noch nicht authentifiziert
|
||||||
|
_userId = int.Parse(claim.Value);
|
||||||
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
|
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
|
||||||
_settings = await TrackerService.GetSettingsAsync();
|
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||||
await LoadWeek();
|
await LoadWeek();
|
||||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,11 +371,11 @@ else
|
|||||||
_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(_monday);
|
var dbDays = await TrackerService.GetWeekAsync(_userId, _monday);
|
||||||
_days = Enumerable.Range(0, 7).Select(i =>
|
_days = Enumerable.Range(0, 7).Select(i =>
|
||||||
{
|
{
|
||||||
var date = _monday.AddDays(i);
|
var date = _monday.AddDays(i);
|
||||||
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date);
|
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
BuildWeekLabels();
|
BuildWeekLabels();
|
||||||
}
|
}
|
||||||
@@ -421,7 +428,7 @@ else
|
|||||||
private async Task SaveDay(DayVm day)
|
private async Task SaveDay(DayVm day)
|
||||||
{
|
{
|
||||||
await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
|
await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
|
||||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||||
BuildWeekLabels();
|
BuildWeekLabels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +493,7 @@ else
|
|||||||
private sealed class DayVm
|
private sealed class DayVm
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
public TimeSpan? Start { get; set; }
|
public TimeSpan? Start { get; set; }
|
||||||
public TimeSpan? End { get; set; }
|
public TimeSpan? End { get; set; }
|
||||||
@@ -500,9 +508,10 @@ else
|
|||||||
|
|
||||||
public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;
|
public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;
|
||||||
|
|
||||||
public static DayVm From(WorkDay? wd, DateOnly date) => new()
|
public static DayVm From(WorkDay? wd, DateOnly date, int userId) => new()
|
||||||
{
|
{
|
||||||
Id = wd?.Id ?? 0,
|
Id = wd?.Id ?? 0,
|
||||||
|
UserId = wd?.UserId ?? userId,
|
||||||
Date = date,
|
Date = date,
|
||||||
Start = wd?.StartTime?.ToTimeSpan(),
|
Start = wd?.StartTime?.ToTimeSpan(),
|
||||||
End = wd?.EndTime?.ToTimeSpan(),
|
End = wd?.EndTime?.ToTimeSpan(),
|
||||||
@@ -517,6 +526,7 @@ else
|
|||||||
public WorkDay ToWorkDay() => new()
|
public WorkDay ToWorkDay() => new()
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
|
UserId = UserId,
|
||||||
Date = Date,
|
Date = Date,
|
||||||
StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
|
StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
|
||||||
EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
|
EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
@page "/login"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@attribute [AllowAnonymous]
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<PageTitle>Anmelden – Timetracker</PageTitle>
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-16">
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||||
|
|
||||||
|
@* ── Logo / Header ── *@
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccessTime"
|
||||||
|
Style="font-size:4rem; color:#1565C0" />
|
||||||
|
<MudText Typo="Typo.h4" Style="font-weight:700; color:#1565C0">Timetracker</MudText>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
||||||
|
<MudTabs @bind-ActivePanelIndex="_activeTab" Rounded="true" Centered="true" Color="Color.Primary">
|
||||||
|
|
||||||
|
@* ── Login ── *@
|
||||||
|
<MudTabPanel Text="Anmelden">
|
||||||
|
<MudStack Spacing="3" Class="mt-4">
|
||||||
|
@if (_error != null && _activeTab == 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||||
|
}
|
||||||
|
<form action="/auth/login" method="post">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudTextField @bind-Value="_loginUsername"
|
||||||
|
Label="Benutzername"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||||
|
name="username"
|
||||||
|
AutoFocus="true" />
|
||||||
|
<MudTextField @bind-Value="_loginPassword"
|
||||||
|
Label="Passwort"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
|
InputType="@(_showLoginPw ? InputType.Text : InputType.Password)"
|
||||||
|
AdornmentAriaLabel="Passwort anzeigen"
|
||||||
|
name="password"
|
||||||
|
OnAdornmentClick="@(() => _showLoginPw = !_showLoginPw)" />
|
||||||
|
<MudButton ButtonType="ButtonType.Submit"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
FullWidth="true"
|
||||||
|
Size="Size.Large"
|
||||||
|
StartIcon="@Icons.Material.Filled.Login">
|
||||||
|
Anmelden
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</form>
|
||||||
|
</MudStack>
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
@* ── Registrieren ── *@
|
||||||
|
<MudTabPanel Text="Registrieren">
|
||||||
|
<MudStack Spacing="3" Class="mt-4">
|
||||||
|
@if (_error != null && _activeTab == 1)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||||
|
}
|
||||||
|
<form action="/auth/register" method="post">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudTextField @bind-Value="_regUsername"
|
||||||
|
Label="Benutzername"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||||
|
name="username"
|
||||||
|
HelperText="Mindestens 3 Zeichen" />
|
||||||
|
<MudTextField @bind-Value="_regPassword"
|
||||||
|
Label="Passwort"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
|
InputType="@(_showRegPw ? InputType.Text : InputType.Password)"
|
||||||
|
AdornmentAriaLabel="Passwort anzeigen"
|
||||||
|
name="password"
|
||||||
|
HelperText="Mindestens 6 Zeichen"
|
||||||
|
OnAdornmentClick="@(() => _showRegPw = !_showRegPw)" />
|
||||||
|
<MudButton ButtonType="ButtonType.Submit"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
FullWidth="true"
|
||||||
|
Size="Size.Large"
|
||||||
|
StartIcon="@Icons.Material.Filled.PersonAdd">
|
||||||
|
Konto erstellen
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</form>
|
||||||
|
</MudStack>
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
</MudTabs>
|
||||||
|
</MudPaper>
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private int _activeTab = 0;
|
||||||
|
private string? _error;
|
||||||
|
private string _loginUsername = "";
|
||||||
|
private string _loginPassword = "";
|
||||||
|
private string _regUsername = "";
|
||||||
|
private string _regPassword = "";
|
||||||
|
private bool _showLoginPw;
|
||||||
|
private bool _showRegPw;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "error")]
|
||||||
|
public string? ErrorParam { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "tab")]
|
||||||
|
public string? TabParam { get; set; }
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
_error = ErrorParam switch
|
||||||
|
{
|
||||||
|
"invalid" => "Benutzername oder Passwort falsch.",
|
||||||
|
not null => Uri.UnescapeDataString(ErrorParam),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
_activeTab = TabParam == "register" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
@page "/month"
|
@page "/month"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
|
|
||||||
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year – Monatsübersicht – Timetracker</PageTitle>
|
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year – Monatsübersicht – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -150,6 +152,7 @@ else
|
|||||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||||
|
|
||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
|
private int _userId;
|
||||||
private int _year = DateTime.Today.Year;
|
private int _year = DateTime.Today.Year;
|
||||||
private int _month = DateTime.Today.Month;
|
private int _month = DateTime.Today.Month;
|
||||||
private List<MonthDayVm> _days = [];
|
private List<MonthDayVm> _days = [];
|
||||||
@@ -167,16 +170,20 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_settings = await TrackerService.GetSettingsAsync();
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
|
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||||
|
if (claim == null) return;
|
||||||
|
_userId = int.Parse(claim.Value);
|
||||||
|
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||||
await LoadMonth();
|
await LoadMonth();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadMonth()
|
private async Task LoadMonth()
|
||||||
{
|
{
|
||||||
var workDays = await TrackerService.GetMonthAsync(_year, _month);
|
var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
|
||||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
|
|
||||||
<PageTitle>Einstellungen – Timetracker</PageTitle>
|
<PageTitle>Einstellungen – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -375,6 +377,7 @@ else
|
|||||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||||
|
|
||||||
private AppSettings? _settings;
|
private AppSettings? _settings;
|
||||||
|
private int _userId;
|
||||||
private int _vacYear = DateTime.Today.Year;
|
private int _vacYear = DateTime.Today.Year;
|
||||||
private List<VacationDay> _vacationDays = [];
|
private List<VacationDay> _vacationDays = [];
|
||||||
private DateTime? _newVacDateFrom;
|
private DateTime? _newVacDateFrom;
|
||||||
@@ -402,14 +405,18 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_settings = await TrackerService.GetSettingsAsync();
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
|
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||||
|
if (claim == null) return;
|
||||||
|
_userId = int.Parse(claim.Value);
|
||||||
|
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||||
await LoadVacations();
|
await LoadVacations();
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadVacations()
|
private async Task LoadVacations()
|
||||||
{
|
{
|
||||||
_vacationDays = await TrackerService.GetVacationDaysAsync(_vacYear);
|
_vacationDays = await TrackerService.GetVacationDaysAsync(_userId, _vacYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangeYear(int delta)
|
private async Task ChangeYear(int delta)
|
||||||
@@ -438,7 +445,7 @@ else
|
|||||||
{
|
{
|
||||||
if (_settings!.IsWorkDay(current.DayOfWeek))
|
if (_settings!.IsWorkDay(current.DayOfWeek))
|
||||||
{
|
{
|
||||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = current, Note = note });
|
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = current, Note = note });
|
||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
current = current.AddDays(1);
|
current = current.AddDays(1);
|
||||||
@@ -452,7 +459,7 @@ else
|
|||||||
|
|
||||||
private async Task RemoveVacation(int id)
|
private async Task RemoveVacation(int id)
|
||||||
{
|
{
|
||||||
await TrackerService.RemoveVacationDayAsync(id);
|
await TrackerService.RemoveVacationDayAsync(_userId, id);
|
||||||
await LoadVacations();
|
await LoadVacations();
|
||||||
Snackbar.Add("Urlaubstag entfernt", Severity.Info);
|
Snackbar.Add("Urlaubstag entfernt", Severity.Info);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@page "/urlaub-maximizer"
|
@page "/urlaub-maximizer"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
|
|
||||||
<PageTitle>Urlaubs-Maximizer – Timetracker</PageTitle>
|
<PageTitle>Urlaubs-Maximizer – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -232,10 +234,15 @@ else
|
|||||||
private int _remainingDays;
|
private int _remainingDays;
|
||||||
private List<Suggestion> _suggestions = [];
|
private List<Suggestion> _suggestions = [];
|
||||||
private string _subLabel = "";
|
private string _subLabel = "";
|
||||||
|
private int _userId;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_settings = await TrackerService.GetSettingsAsync();
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
|
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||||
|
if (claim == null) return;
|
||||||
|
_userId = int.Parse(claim.Value);
|
||||||
|
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||||
await LoadYear();
|
await LoadYear();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
@@ -243,7 +250,7 @@ else
|
|||||||
private async Task LoadYear()
|
private async Task LoadYear()
|
||||||
{
|
{
|
||||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||||
_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);
|
||||||
@@ -262,7 +269,7 @@ else
|
|||||||
private async Task TakeSuggestion(Suggestion s)
|
private async Task TakeSuggestion(Suggestion s)
|
||||||
{
|
{
|
||||||
foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d)))
|
foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d)))
|
||||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = d, Note = "Urlaubs-Maximizer" });
|
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = d, Note = "Urlaubs-Maximizer" });
|
||||||
await LoadYear();
|
await LoadYear();
|
||||||
var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage";
|
var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage";
|
||||||
Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen – {s.TotalFreeDays} Tage frei!", Severity.Success);
|
Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen – {s.TotalFreeDays} Tage frei!", Severity.Success);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
|
@code {
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
var returnUrl = Uri.EscapeDataString(Nav.Uri);
|
||||||
|
Nav.NavigateTo($"/login?returnUrl={returnUrl}", forceLoad: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||||
|
<NotAuthorized>
|
||||||
|
<RedirectToLogin />
|
||||||
|
</NotAuthorized>
|
||||||
|
</AuthorizeRouteView>
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
</Found>
|
</Found>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
|
@using System.Security.Claims
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
|||||||
public class AppSettings
|
public class AppSettings
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
public double DailyTargetHours { get; set; } = 7.5;
|
public double DailyTargetHours { get; set; } = 7.5;
|
||||||
public int MinimumBreakMinutes { get; set; } = 30;
|
public int MinimumBreakMinutes { get; set; } = 30;
|
||||||
public int VacationDaysPerYear { get; set; } = 30;
|
public int VacationDaysPerYear { get; set; } = 30;
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace timetracker.Data;
|
||||||
|
|
||||||
|
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||||
|
{
|
||||||
|
public async Task<User?> LoginAsync(string username, string password)
|
||||||
|
{
|
||||||
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
|
var user = await db.Users
|
||||||
|
.FirstOrDefaultAsync(u => u.Username == username);
|
||||||
|
if (user == null) return null;
|
||||||
|
return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(User? User, string? Error)> RegisterAsync(string username, string password)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
|
||||||
|
return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
|
||||||
|
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
|
||||||
|
return (null, "Passwort muss mindestens 6 Zeichen lang sein.");
|
||||||
|
|
||||||
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
|
if (await db.Users.AnyAsync(u => u.Username == username))
|
||||||
|
return (null, "Benutzername bereits vergeben.");
|
||||||
|
|
||||||
|
var (hash, salt) = HashPassword(password);
|
||||||
|
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
|
||||||
|
db.Users.Add(user);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return (user, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string hash, string salt) HashPassword(string password)
|
||||||
|
{
|
||||||
|
var saltBytes = RandomNumberGenerator.GetBytes(32);
|
||||||
|
var salt = Convert.ToBase64String(saltBytes);
|
||||||
|
var hash = ComputeHash(password, salt);
|
||||||
|
return (hash, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool VerifyPassword(string password, string hash, string salt)
|
||||||
|
=> ComputeHash(password, salt) == hash;
|
||||||
|
|
||||||
|
private static string ComputeHash(string password, string salt)
|
||||||
|
{
|
||||||
|
var hash = Rfc2898DeriveBytes.Pbkdf2(
|
||||||
|
Encoding.UTF8.GetBytes(password),
|
||||||
|
Convert.FromBase64String(salt),
|
||||||
|
iterations: 200_000,
|
||||||
|
HashAlgorithmName.SHA256,
|
||||||
|
outputLength: 32);
|
||||||
|
return Convert.ToBase64String(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using timetracker.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace timetracker.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TimetrackerDbContext))]
|
||||||
|
[Migration("20260522081459_AddMultiUser")]
|
||||||
|
partial class AddMultiUser
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.AppSettings", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("DailyTargetHours")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("MinimumBreakMinutes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VacationDaysPerYear")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkFriday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkMonday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkSaturday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkSunday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkThursday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkTuesday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkWednesday")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("AppSettings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<TimeOnly?>("EndTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<TimeOnly?>("StartTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("WorkDayId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("WorkDayId");
|
||||||
|
|
||||||
|
b.ToTable("BreakEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PublicHolidays");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordSalt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("VacationDays");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<TimeOnly?>("EndTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<TimeOnly?>("StartTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("WorkDays");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("timetracker.Data.WorkDay", "WorkDay")
|
||||||
|
.WithMany("Breaks")
|
||||||
|
.HasForeignKey("WorkDayId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("WorkDay");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Breaks");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace timetracker.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMultiUser : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "UserId",
|
||||||
|
table: "WorkDays",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "UserId",
|
||||||
|
table: "VacationDays",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "UserId",
|
||||||
|
table: "AppSettings",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Username = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
PasswordHash = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
PasswordSalt = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UserId",
|
||||||
|
table: "WorkDays");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UserId",
|
||||||
|
table: "VacationDays");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UserId",
|
||||||
|
table: "AppSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ namespace timetracker.Data.Migrations
|
|||||||
b.Property<int>("MinimumBreakMinutes")
|
b.Property<int>("MinimumBreakMinutes")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("VacationDaysPerYear")
|
b.Property<int>("VacationDaysPerYear")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@@ -58,24 +61,6 @@ namespace timetracker.Data.Migrations
|
|||||||
b.ToTable("AppSettings");
|
b.ToTable("AppSettings");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("Date")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("PublicHolidays");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -98,6 +83,47 @@ namespace timetracker.Data.Migrations
|
|||||||
b.ToTable("BreakEntries");
|
b.ToTable("BreakEntries");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PublicHolidays");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("timetracker.Data.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordSalt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -110,6 +136,9 @@ namespace timetracker.Data.Migrations
|
|||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("VacationDays");
|
b.ToTable("VacationDays");
|
||||||
@@ -130,6 +159,9 @@ namespace timetracker.Data.Migrations
|
|||||||
b.Property<TimeOnly?>("StartTime")
|
b.Property<TimeOnly?>("StartTime")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("WorkDays");
|
b.ToTable("WorkDays");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace timetracker.Data;
|
|||||||
|
|
||||||
public class TimetrackerDbContext(DbContextOptions<TimetrackerDbContext> options) : DbContext(options)
|
public class TimetrackerDbContext(DbContextOptions<TimetrackerDbContext> options) : DbContext(options)
|
||||||
{
|
{
|
||||||
|
public DbSet<User> Users => Set<User>();
|
||||||
public DbSet<WorkDay> WorkDays => Set<WorkDay>();
|
public DbSet<WorkDay> WorkDays => Set<WorkDay>();
|
||||||
public DbSet<BreakEntry> BreakEntries => Set<BreakEntry>();
|
public DbSet<BreakEntry> BreakEntries => Set<BreakEntry>();
|
||||||
public DbSet<AppSettings> AppSettings => Set<AppSettings>();
|
public DbSet<AppSettings> AppSettings => Set<AppSettings>();
|
||||||
|
|||||||
+16
-16
@@ -4,12 +4,12 @@ namespace timetracker.Data;
|
|||||||
|
|
||||||
public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||||
{
|
{
|
||||||
public async Task<List<WorkDay>> GetWeekAsync(DateOnly monday)
|
public async Task<List<WorkDay>> GetWeekAsync(int userId, DateOnly monday)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
return await db.WorkDays
|
return await db.WorkDays
|
||||||
.Include(w => w.Breaks)
|
.Include(w => w.Breaks)
|
||||||
.Where(w => w.Date >= monday && w.Date < monday.AddDays(7))
|
.Where(w => w.UserId == userId && w.Date >= monday && w.Date < monday.AddDays(7))
|
||||||
.OrderBy(w => w.Date)
|
.OrderBy(w => w.Date)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var existing = await db.WorkDays
|
var existing = await db.WorkDays
|
||||||
.Include(w => w.Breaks)
|
.Include(w => w.Breaks)
|
||||||
.FirstOrDefaultAsync(w => w.Date == workDay.Date);
|
.FirstOrDefaultAsync(w => w.UserId == workDay.UserId && w.Date == workDay.Date);
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
@@ -45,17 +45,17 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AppSettings> GetSettingsAsync()
|
public async Task<AppSettings> GetSettingsAsync(int userId)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
return await db.AppSettings.FindAsync(1) ?? new AppSettings { Id = 1 };
|
return await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == userId)
|
||||||
|
?? new AppSettings { UserId = userId };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveSettingsAsync(AppSettings settings)
|
public async Task SaveSettingsAsync(AppSettings settings)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
settings.Id = 1;
|
var existing = await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == settings.UserId);
|
||||||
var existing = await db.AppSettings.FindAsync(1);
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
db.AppSettings.Add(settings);
|
db.AppSettings.Add(settings);
|
||||||
else
|
else
|
||||||
@@ -75,11 +75,11 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Urlaub ────────────────────────────────────────────────────────────
|
// ── Urlaub ────────────────────────────────────────────────────────────
|
||||||
public async Task<List<VacationDay>> GetVacationDaysAsync(int year)
|
public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
return await db.VacationDays
|
return await db.VacationDays
|
||||||
.Where(v => v.Date.Year == year)
|
.Where(v => v.UserId == userId && v.Date.Year == year)
|
||||||
.OrderBy(v => v.Date)
|
.OrderBy(v => v.Date)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
public async Task AddVacationDayAsync(VacationDay vacationDay)
|
public async Task AddVacationDayAsync(VacationDay vacationDay)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var exists = await db.VacationDays.AnyAsync(v => v.Date == vacationDay.Date);
|
var exists = await db.VacationDays.AnyAsync(v => v.UserId == vacationDay.UserId && v.Date == vacationDay.Date);
|
||||||
if (!exists)
|
if (!exists)
|
||||||
{
|
{
|
||||||
vacationDay.Id = 0;
|
vacationDay.Id = 0;
|
||||||
@@ -96,10 +96,10 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveVacationDayAsync(int id)
|
public async Task RemoveVacationDayAsync(int userId, int id)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var v = await db.VacationDays.FindAsync(id);
|
var v = await db.VacationDays.FirstOrDefaultAsync(v => v.Id == id && v.UserId == userId);
|
||||||
if (v != null)
|
if (v != null)
|
||||||
{
|
{
|
||||||
db.VacationDays.Remove(v);
|
db.VacationDays.Remove(v);
|
||||||
@@ -108,12 +108,12 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Gleitzeitkonto ───────────────────────────────────────────────────
|
// ── Gleitzeitkonto ───────────────────────────────────────────────────
|
||||||
public async Task<TimeSpan> GetTotalOvertimeAsync(AppSettings settings)
|
public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var allDays = await db.WorkDays
|
var allDays = await db.WorkDays
|
||||||
.Include(w => w.Breaks)
|
.Include(w => w.Breaks)
|
||||||
.Where(w => w.StartTime != null && w.EndTime != null)
|
.Where(w => w.UserId == userId && w.StartTime != null && w.EndTime != null)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var total = TimeSpan.Zero;
|
var total = TimeSpan.Zero;
|
||||||
@@ -132,14 +132,14 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Monatsübersicht ───────────────────────────────────────────────────
|
// ── Monatsübersicht ───────────────────────────────────────────────────
|
||||||
public async Task<List<WorkDay>> GetMonthAsync(int year, int month)
|
public async Task<List<WorkDay>> GetMonthAsync(int userId, int year, int month)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var from = new DateOnly(year, month, 1);
|
var from = new DateOnly(year, month, 1);
|
||||||
var to = from.AddMonths(1);
|
var to = from.AddMonths(1);
|
||||||
return await db.WorkDays
|
return await db.WorkDays
|
||||||
.Include(w => w.Breaks)
|
.Include(w => w.Breaks)
|
||||||
.Where(w => w.Date >= from && w.Date < to)
|
.Where(w => w.UserId == userId && w.Date >= from && w.Date < to)
|
||||||
.OrderBy(w => w.Date)
|
.OrderBy(w => w.Date)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace timetracker.Data;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public string PasswordHash { get; set; } = "";
|
||||||
|
public string PasswordSalt { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
|||||||
public class VacationDay
|
public class VacationDay
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
public string? Note { get; set; }
|
public string? Note { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
|||||||
public class WorkDay
|
public class WorkDay
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
public TimeOnly? StartTime { get; set; }
|
public TimeOnly? StartTime { get; set; }
|
||||||
public TimeOnly? EndTime { get; set; }
|
public TimeOnly? EndTime { get; set; }
|
||||||
|
|||||||
+66
@@ -1,3 +1,6 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using timetracker.Components;
|
using timetracker.Components;
|
||||||
@@ -5,6 +8,19 @@ using timetracker.Data;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||||
|
.AddCookie(options =>
|
||||||
|
{
|
||||||
|
options.LoginPath = "/login";
|
||||||
|
options.LogoutPath = "/auth/logout";
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||||
|
options.SlidingExpiration = true;
|
||||||
|
});
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
builder.Services.AddScoped<AuthService>();
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
.AddInteractiveServerComponents();
|
.AddInteractiveServerComponents();
|
||||||
@@ -39,10 +55,60 @@ if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelop
|
|||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
|
|
||||||
app.MapStaticAssets();
|
app.MapStaticAssets();
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
// ── Auth-Endpoints ────────────────────────────────────────────────────────────
|
||||||
|
app.MapPost("/auth/login", async (HttpContext ctx, AuthService authService) =>
|
||||||
|
{
|
||||||
|
var form = await ctx.Request.ReadFormAsync();
|
||||||
|
var username = form["username"].ToString();
|
||||||
|
var password = form["password"].ToString();
|
||||||
|
var user = await authService.LoginAsync(username, password);
|
||||||
|
if (user == null)
|
||||||
|
return Results.Redirect("/login?error=invalid");
|
||||||
|
|
||||||
|
var claims = new[] {
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
|
new Claim(ClaimTypes.Name, user.Username)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
|
new ClaimsPrincipal(identity),
|
||||||
|
new AuthenticationProperties { IsPersistent = true });
|
||||||
|
return Results.Redirect("/");
|
||||||
|
}).DisableAntiforgery();
|
||||||
|
|
||||||
|
app.MapPost("/auth/register", async (HttpContext ctx, AuthService authService) =>
|
||||||
|
{
|
||||||
|
var form = await ctx.Request.ReadFormAsync();
|
||||||
|
var username = form["username"].ToString();
|
||||||
|
var password = form["password"].ToString();
|
||||||
|
var (user, error) = await authService.RegisterAsync(username, password);
|
||||||
|
if (user == null)
|
||||||
|
return Results.Redirect($"/login?tab=register&error={Uri.EscapeDataString(error!)}");
|
||||||
|
|
||||||
|
var claims = new[] {
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
|
new Claim(ClaimTypes.Name, user.Username)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
|
new ClaimsPrincipal(identity),
|
||||||
|
new AuthenticationProperties { IsPersistent = true });
|
||||||
|
return Results.Redirect("/");
|
||||||
|
}).DisableAntiforgery();
|
||||||
|
|
||||||
|
app.MapGet("/auth/logout", async (HttpContext ctx) =>
|
||||||
|
{
|
||||||
|
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
return Results.Redirect("/login");
|
||||||
|
});
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}}
|
||||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("timetracker")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("timetracker")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0467f450368a3b05b63a5d2a2cc8779571042711")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+64c5f6aa2c77c868467dca487ae6b581bca6862d")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("timetracker")]
|
[assembly: System.Reflection.AssemblyProductAttribute("timetracker")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("timetracker")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("timetracker")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
d82bd1af2676da228608191aea963cac3166fa85ba461e9bcfa8554e5f0bdffb
|
452eedcfc45da63be9bf8c68bebf3387bcd4a96ecf3b483a73c4affbb5b93b4e
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ build_metadata.AdditionalFiles.CssScope =
|
|||||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xIb21lLnJhem9y
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xIb21lLnJhem9y
|
||||||
build_metadata.AdditionalFiles.CssScope =
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
|
|
||||||
|
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Login.razor]
|
||||||
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xMb2dpbi5yYXpvcg==
|
||||||
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
|
|
||||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Month.razor]
|
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Month.razor]
|
||||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xNb250aC5yYXpvcg==
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xNb250aC5yYXpvcg==
|
||||||
build_metadata.AdditionalFiles.CssScope =
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
@@ -66,6 +70,10 @@ build_metadata.AdditionalFiles.CssScope =
|
|||||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xVcmxhdWJzTWF4aW1pemVyLnJhem9y
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xVcmxhdWJzTWF4aW1pemVyLnJhem9y
|
||||||
build_metadata.AdditionalFiles.CssScope =
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
|
|
||||||
|
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/RedirectToLogin.razor]
|
||||||
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSZWRpcmVjdFRvTG9naW4ucmF6b3I=
|
||||||
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
|
|
||||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Routes.razor]
|
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Routes.razor]
|
||||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSb3V0ZXMucmF6b3I=
|
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSb3V0ZXMucmF6b3I=
|
||||||
build_metadata.AdditionalFiles.CssScope =
|
build_metadata.AdditionalFiles.CssScope =
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
ec4c252fd2023b232a1f6088ccec5cbdd1025e488be8a7c43e01c6aa2c479e89
|
73be10256eedd125d5f8f4d2e9dd0d1388a3fccf5758d7c2e52426f3812e17fd
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user