Files
timetracker/timetracker.Client/Components/Pages/Dashboard.razor
T

231 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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}";
}
}