231 lines
11 KiB
Plaintext
231 lines
11 KiB
Plaintext
@page "/"
|
||
@rendermode InteractiveWebAssembly
|
||
@attribute [Authorize]
|
||
@inject ITimetrackerService TrackerService
|
||
@inject AuthenticationStateProvider AuthStateProvider
|
||
@inject IJSRuntime JSRuntime
|
||
|
||
<PageTitle>Dashboard – Timetracker</PageTitle>
|
||
|
||
@if (_loading)
|
||
{
|
||
<MudStack AlignItems="AlignItems.Center" Class="mt-16" Spacing="3">
|
||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||
<MudText Color="Color.Secondary">Lade Dashboard…</MudText>
|
||
</MudStack>
|
||
}
|
||
else
|
||
{
|
||
<MudStack Spacing="4">
|
||
|
||
@* ── Header & Einstellungen ── *@
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||
<MudMenu Icon="@Icons.Material.Filled.Settings" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight" Dense="true" Color="Color.Secondary">
|
||
<MudMenuItem OnClick="@(() => ToggleWidget("EarlyBird"))">
|
||
<MudCheckBox T="bool" Value="@_showEarlyBird" Label="Early Bird anzeigen" Color="Color.Primary" ReadOnly="true"/>
|
||
</MudMenuItem>
|
||
<MudMenuItem OnClick="@(() => ToggleWidget("NightOwl"))">
|
||
<MudCheckBox T="bool" Value="@_showNightOwl" Label="Night Owl anzeigen" Color="Color.Primary" ReadOnly="true"/>
|
||
</MudMenuItem>
|
||
<MudMenuItem OnClick="@(() => ToggleWidget("Pausenkonig"))">
|
||
<MudCheckBox T="bool" Value="@_showPausenkonig" Label="Pausenkönig anzeigen" Color="Color.Primary" ReadOnly="true"/>
|
||
</MudMenuItem>
|
||
<MudMenuItem OnClick="@(() => ToggleWidget("Streaks"))">
|
||
<MudCheckBox T="bool" Value="@_showStreaks" Label="Streaks anzeigen" Color="Color.Primary" ReadOnly="true"/>
|
||
</MudMenuItem>
|
||
<MudDivider />
|
||
<MudMenuItem OnClick="@(() => ToggleWidget("Charts"))">
|
||
<MudCheckBox T="bool" Value="@_showCharts" Label="Diagramme anzeigen" Color="Color.Primary" ReadOnly="true"/>
|
||
</MudMenuItem>
|
||
</MudMenu>
|
||
</MudStack>
|
||
|
||
@* ── Gamification Widgets ── *@
|
||
<MudGrid>
|
||
@if (_showEarlyBird)
|
||
{
|
||
<MudItem xs="12" sm="6" md="3">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl" Style="background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudAvatar Color="Color.Warning" Variant="Variant.Filled" Size="Size.Large">🌅</MudAvatar>
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Early Bird</MudText>
|
||
<MudText Typo="Typo.h5" Style="font-weight: 700">@FormatTimeSpan(_stats.EarliestStart)</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">Frühester Beginn im Monat</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
</MudItem>
|
||
}
|
||
|
||
@if (_showNightOwl)
|
||
{
|
||
<MudItem xs="12" sm="6" md="3">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl" Style="background: linear-gradient(135deg, #304352 0%, #d7d2cc 100%); color: white;">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudAvatar Color="Color.Dark" Variant="Variant.Filled" Size="Size.Large">🦉</MudAvatar>
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.subtitle2" Style="opacity: 0.8">Night Owl</MudText>
|
||
<MudText Typo="Typo.h5" Style="font-weight: 700">@FormatTimeSpan(_stats.LatestEnd)</MudText>
|
||
<MudText Typo="Typo.caption" Style="opacity: 0.8">Spätestes Ende im Monat</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
</MudItem>
|
||
}
|
||
|
||
@if (_showPausenkonig)
|
||
{
|
||
<MudItem xs="12" sm="6" md="3">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl" Style="background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudAvatar Color="Color.Info" Variant="Variant.Filled" Size="Size.Large">☕</MudAvatar>
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Pausenkönig</MudText>
|
||
<MudText Typo="Typo.h5" Style="font-weight: 700">@((int)_stats.AverageBreak.TotalMinutes) Min.</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">Ø Pause im Monat</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
</MudItem>
|
||
}
|
||
|
||
@if (_showStreaks)
|
||
{
|
||
<MudItem xs="12" sm="6" md="3">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl" Style="background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); color: white;">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudAvatar Color="Color.Error" Variant="Variant.Filled" Size="Size.Large">🔥</MudAvatar>
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.subtitle2" Style="opacity: 0.9">Streak</MudText>
|
||
<MudText Typo="Typo.h5" Style="font-weight: 700">@_stats.CurrentStreak / @_stats.LongestStreak</MudText>
|
||
<MudText Typo="Typo.caption" Style="opacity: 0.9">Aktuell / Bester (Tage)</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
</MudItem>
|
||
}
|
||
</MudGrid>
|
||
|
||
@* ── Diagramme ── *@
|
||
@if (_showCharts)
|
||
{
|
||
<MudGrid>
|
||
<MudItem xs="12" md="8">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl h-100">
|
||
<MudText Typo="Typo.h6" Class="mb-4">Gleitzeit-Verlauf @DateTime.Today.Year</MudText>
|
||
<MudChart T="double" ChartType="ChartType.Bar"
|
||
ChartSeries="@_barChartSeries"
|
||
XAxisLabels="@_barChartLabels"
|
||
Width="100%" Height="300px" />
|
||
</MudPaper>
|
||
</MudItem>
|
||
|
||
<MudItem xs="12" md="4">
|
||
<MudPaper Elevation="2" Class="pa-4 rounded-xl h-100">
|
||
<MudText Typo="Typo.h6" Class="mb-4">Jahres-Verteilung</MudText>
|
||
<MudChart T="double" ChartType="ChartType.Pie"
|
||
InputData="@_pieChartData"
|
||
InputLabels="@_pieChartLabels"
|
||
Width="100%" Height="300px" />
|
||
</MudPaper>
|
||
</MudItem>
|
||
</MudGrid>
|
||
}
|
||
</MudStack>
|
||
}
|
||
|
||
@code {
|
||
private bool _loading = true;
|
||
private DashboardStatsVm _stats = new();
|
||
private DashboardChartDataVm _chartData = new();
|
||
|
||
private bool _showEarlyBird = true;
|
||
private bool _showNightOwl = true;
|
||
private bool _showPausenkonig = true;
|
||
private bool _showStreaks = true;
|
||
private bool _showCharts = true;
|
||
|
||
private List<ChartSeries<double>> _barChartSeries = new();
|
||
private string[] _barChartLabels = Array.Empty<string>();
|
||
|
||
private double[] _pieChartData = Array.Empty<double>();
|
||
private string[] _pieChartLabels = Array.Empty<string>();
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||
var claim = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||
if (claim == null) return;
|
||
|
||
int userId = int.Parse(claim.Value);
|
||
int year = DateTime.Today.Year;
|
||
int month = DateTime.Today.Month;
|
||
|
||
await LoadSettings();
|
||
|
||
_stats = await TrackerService.GetDashboardStatsAsync(userId, year, month);
|
||
_chartData = await TrackerService.GetDashboardChartDataAsync(userId, year);
|
||
|
||
// Prepare Bar Chart
|
||
_barChartSeries = new List<ChartSeries<double>>
|
||
{
|
||
new ChartSeries<double> { Name = "Überstunden", Data = _chartData.MonthlyOvertime.Select(m => m.OvertimeHours).ToArray() }
|
||
};
|
||
_barChartLabels = _chartData.MonthlyOvertime.Select(m => m.MonthName).ToArray();
|
||
|
||
// Prepare Pie Chart
|
||
_pieChartData = new double[] { _chartData.WorkDaysCount, _chartData.VacationDaysCount, _chartData.PublicHolidaysCount };
|
||
_pieChartLabels = new string[] { "Arbeit", "Urlaub", "Feiertage" };
|
||
|
||
_loading = false;
|
||
}
|
||
|
||
private async Task LoadSettings()
|
||
{
|
||
try
|
||
{
|
||
var eb = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "widget_EarlyBird");
|
||
if (eb != null) _showEarlyBird = eb == "true";
|
||
|
||
var no = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "widget_NightOwl");
|
||
if (no != null) _showNightOwl = no == "true";
|
||
|
||
var pk = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "widget_Pausenkonig");
|
||
if (pk != null) _showPausenkonig = pk == "true";
|
||
|
||
var st = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "widget_Streaks");
|
||
if (st != null) _showStreaks = st == "true";
|
||
|
||
var ch = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "widget_Charts");
|
||
if (ch != null) _showCharts = ch == "true";
|
||
}
|
||
catch
|
||
{
|
||
// SSR Fallback
|
||
}
|
||
}
|
||
|
||
private async Task ToggleWidget(string widget)
|
||
{
|
||
bool newState = true;
|
||
if (widget == "EarlyBird") { _showEarlyBird = !_showEarlyBird; newState = _showEarlyBird; }
|
||
if (widget == "NightOwl") { _showNightOwl = !_showNightOwl; newState = _showNightOwl; }
|
||
if (widget == "Pausenkonig") { _showPausenkonig = !_showPausenkonig; newState = _showPausenkonig; }
|
||
if (widget == "Streaks") { _showStreaks = !_showStreaks; newState = _showStreaks; }
|
||
if (widget == "Charts") { _showCharts = !_showCharts; newState = _showCharts; }
|
||
|
||
try
|
||
{
|
||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", $"widget_{widget}", newState.ToString().ToLower());
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
private string FormatTimeSpan(TimeSpan? ts)
|
||
{
|
||
if (!ts.HasValue) return "-";
|
||
return $"{ts.Value.Hours:D2}:{ts.Value.Minutes:D2}";
|
||
}
|
||
}
|