From 20295243799b448ad3a6a19b1769656c3cae3865 Mon Sep 17 00:00:00 2001 From: MarcWieland Date: Mon, 8 Jun 2026 23:52:22 +0200 Subject: [PATCH] Design updates --- .../Components/Layout/ChatbotWidget.razor.css | 22 +- .../Components/Layout/MainLayout.razor | 20 +- .../Components/Layout/NavMenu.razor | 29 +- .../Components/Pages/AdminUsers.razor | 2 +- .../Components/Pages/Changelog.razor | 5 +- .../Components/Pages/Feiertage.razor | 4 +- .../Components/Pages/Home.razor | 6 +- .../Components/Pages/Login.razor | 4 +- .../Components/Pages/Month.razor | 6 +- .../Components/Pages/Settings.razor | 2 +- .../Components/Pages/Stats.razor | 382 ++++++++++++++++++ .../Components/Pages/UrlaubsMaximizer.razor | 2 +- 12 files changed, 436 insertions(+), 48 deletions(-) create mode 100644 timetracker.Client/Components/Pages/Stats.razor diff --git a/timetracker.Client/Components/Layout/ChatbotWidget.razor.css b/timetracker.Client/Components/Layout/ChatbotWidget.razor.css index 823d646..5da3cf6 100644 --- a/timetracker.Client/Components/Layout/ChatbotWidget.razor.css +++ b/timetracker.Client/Components/Layout/ChatbotWidget.razor.css @@ -14,13 +14,13 @@ align-items: center; justify-content: center; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - border: 2px solid #3F51B5; + border: 2px solid #0EA5E9; } .timebot-launcher:hover { transform: scale(1.1) translateY(-4px); - box-shadow: 0 12px 28px rgba(63, 81, 181, 0.4); - border-color: #1A237E; + box-shadow: 0 12px 28px rgba(14, 165, 233, 0.4); + border-color: #0284C7; } .timebot-launcher img { @@ -83,7 +83,7 @@ /* Header */ .timebot-header { - background: linear-gradient(135deg, #3F51B5 0%, #1A237E 100%); + background: #0F172A; padding: 14px 16px; display: flex; align-items: center; @@ -211,10 +211,10 @@ } .user-bubble { - background: linear-gradient(135deg, #3F51B5 0%, #1A237E 100%); + background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%); color: white; border-bottom-right-radius: 4px; - box-shadow: 0 4px 12px rgba(63, 81, 181, 0.2); + box-shadow: 0 4px 12px rgba(14, 165, 233, 0.2); } /* Typing Indicator */ @@ -271,8 +271,8 @@ } .timebot-suggestions button:hover { - background: rgba(63, 81, 181, 0.15); - border-color: #3F51B5; + background: rgba(14, 165, 233, 0.15); + border-color: #0EA5E9; color: white; transform: translateY(-1px); } @@ -300,9 +300,9 @@ } .timebot-input:focus { - border-color: #3F51B5; + border-color: #0EA5E9; background: rgba(255, 255, 255, 0.1); - box-shadow: 0 0 0 2px rgba(63, 81, 181, 0.2); + box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2); } .timebot-send-btn { @@ -319,7 +319,7 @@ } .timebot-send-btn:hover { - background: #e3e6ff; + background: #e0f2fe; transform: scale(1.05); } diff --git a/timetracker.Client/Components/Layout/MainLayout.razor b/timetracker.Client/Components/Layout/MainLayout.razor index 6c0990a..2a28816 100644 --- a/timetracker.Client/Components/Layout/MainLayout.razor +++ b/timetracker.Client/Components/Layout/MainLayout.razor @@ -11,7 +11,7 @@ - + @@ -64,17 +64,17 @@ { PaletteLight = new PaletteLight { - Primary = "#3F51B5", - PrimaryDarken = "#1A237E", - PrimaryLighten = "#7986CB", - Secondary = "#009688", - SecondaryDarken = "#00695C", - AppbarBackground = "#3F51B5", - Background = "#F4F6F9", + Primary = "#1E293B", + PrimaryDarken = "#0F172A", + PrimaryLighten = "#475569", + Secondary = "#0EA5E9", + SecondaryDarken = "#0284C7", + AppbarBackground = "#0F172A", + Background = "#F8FAFC", DrawerBackground = "#FFFFFF", Surface = "#FFFFFF", - TextPrimary = "#212121", - TextSecondary = "#757575", + TextPrimary = "#0F172A", + TextSecondary = "#475569", } }; diff --git a/timetracker.Client/Components/Layout/NavMenu.razor b/timetracker.Client/Components/Layout/NavMenu.razor index 27c3dee..543c2f5 100644 --- a/timetracker.Client/Components/Layout/NavMenu.razor +++ b/timetracker.Client/Components/Layout/NavMenu.razor @@ -1,8 +1,9 @@ -
+
Wochenübersicht Monatsübersicht + Statistiken Feiertage Urlaubs-Maximizer Einstellungen @@ -16,21 +17,25 @@ - - - @context.User.Identity?.Name - - - Abmelden - + @* Username Display - Styled to match NavLink layout exactly *@ + - - + + + + + Abmelden + + + Version 1.4 - - + +
\ No newline at end of file diff --git a/timetracker.Client/Components/Pages/AdminUsers.razor b/timetracker.Client/Components/Pages/AdminUsers.razor index 5039fbc..71cf2fd 100644 --- a/timetracker.Client/Components/Pages/AdminUsers.razor +++ b/timetracker.Client/Components/Pages/AdminUsers.razor @@ -22,7 +22,7 @@ else @* ── Header ── *@ + Style="background: #991B1B; color:white;"> diff --git a/timetracker.Client/Components/Pages/Changelog.razor b/timetracker.Client/Components/Pages/Changelog.razor index 1a206ea..49d4603 100644 --- a/timetracker.Client/Components/Pages/Changelog.razor +++ b/timetracker.Client/Components/Pages/Changelog.razor @@ -8,7 +8,7 @@ @* ── Header ── *@ + Style="background: #1E293B; color:white;"> @@ -71,7 +71,8 @@ new("1.4", "08.06.2026", true, [ new("Neu", "Timebot implementiert"), - new("Upgrade", "Security hardening") + new("Upgrade", "Security hardening"), + new("Upgrade", "Changed coloring") ]), new("1.3", "08.06.2026", false, [ diff --git a/timetracker.Client/Components/Pages/Feiertage.razor b/timetracker.Client/Components/Pages/Feiertage.razor index 7e2debc..7f41223 100644 --- a/timetracker.Client/Components/Pages/Feiertage.razor +++ b/timetracker.Client/Components/Pages/Feiertage.razor @@ -20,7 +20,7 @@ else @* ── Header ── *@ + Style="background: #0F766E; color: white;"> @@ -140,7 +140,7 @@ else @* ── Zusammenfassung ── *@ + Style="background: linear-gradient(90deg, rgba(15,118,110,0.08) 0%, transparent 100%); border-left: 4px solid #0F766E;"> diff --git a/timetracker.Client/Components/Pages/Home.razor b/timetracker.Client/Components/Pages/Home.razor index 21e8dd4..8dee457 100644 --- a/timetracker.Client/Components/Pages/Home.razor +++ b/timetracker.Client/Components/Pages/Home.razor @@ -21,7 +21,7 @@ else @* ── Wochen-Header ── *@ + Style="background: #1E293B; color: white;"> @@ -95,7 +95,7 @@ else @* ── Arbeitstag: vollständige Karte ── *@ - + @@ -270,7 +270,7 @@ else @* ── Wochensumme ── *@ + Style="background: #0F172A; color:white;"> Wochensumme diff --git a/timetracker.Client/Components/Pages/Login.razor b/timetracker.Client/Components/Pages/Login.razor index 617d1bc..93d765b 100644 --- a/timetracker.Client/Components/Pages/Login.razor +++ b/timetracker.Client/Components/Pages/Login.razor @@ -12,8 +12,8 @@ @* ── Logo / Header ── *@ - Timetracker + Style="font-size:4rem; color:#0EA5E9" /> + Timetracker diff --git a/timetracker.Client/Components/Pages/Month.razor b/timetracker.Client/Components/Pages/Month.razor index 4c9628c..41afa6d 100644 --- a/timetracker.Client/Components/Pages/Month.razor +++ b/timetracker.Client/Components/Pages/Month.razor @@ -20,7 +20,7 @@ else @* ── Monats-Header ── *@ + Style="background: #1E293B; color: white;"> @@ -51,7 +51,7 @@ else - + Tag Start Ende @@ -95,7 +95,7 @@ else @* ── Monatszusammenfassung ── *@ + Style="background: linear-gradient(135deg, rgba(14,165,233,0.08) 0%, rgba(14,165,233,0.02) 100%); border-left: 6px solid #0EA5E9;"> Monatszusammenfassung diff --git a/timetracker.Client/Components/Pages/Settings.razor b/timetracker.Client/Components/Pages/Settings.razor index 716e1f4..2da5397 100644 --- a/timetracker.Client/Components/Pages/Settings.razor +++ b/timetracker.Client/Components/Pages/Settings.razor @@ -20,7 +20,7 @@ else @* ── Header ── *@ + Style="background: #1E293B; color:white;"> diff --git a/timetracker.Client/Components/Pages/Stats.razor b/timetracker.Client/Components/Pages/Stats.razor new file mode 100644 index 0000000..3650eef --- /dev/null +++ b/timetracker.Client/Components/Pages/Stats.razor @@ -0,0 +1,382 @@ +@page "/stats" +@rendermode InteractiveWebAssembly +@attribute [Authorize] +@inject ITimetrackerService TrackerService +@inject AuthenticationStateProvider AuthStateProvider +@using System.Security.Claims +@using timetracker.Shared + +Statistiken – Timetracker + +@if (_loading) +{ + + + Lade Statistiken… + +} +else +{ + + + @* ── Header ── *@ + + + + + Statistiken + Auswertung deiner Arbeitsleistung und Gleitzeitkonto + + + + + + @* ── Card 1: Wochen-Fortschritt ── *@ + + + + + Wochenfortschritt + + @{ + var pct = _weekTargetHours > 0 ? (int)Math.Min((_weekWorkedHours / _weekTargetHours) * 100, 100) : 0; + var displayPct = _weekTargetHours > 0 ? (int)Math.Round((_weekWorkedHours / _weekTargetHours) * 100) : 0; + } + +
+ +
+ @displayPct% + Erreicht +
+
+ + + + Arbeitszeit (Ist): + @FormatHours(_weekWorkedHours) Std. + + + Wochensoll (Soll): + @FormatHours(_weekTargetHours) Std. + + +
+
+
+
+ + @* ── Card 2: Wochensaldo ── *@ + + + + + Wochensaldo + + + = 0 ? "#10B981" : "#EF4444")}; font-size:3rem")" /> + + = 0 ? "#10B981" : "#EF4444")};")"> + @FormatTs(TimeSpan.FromHours(_weekOvertimeHours), sign: true) + + Überstunden-Veränderung diese Woche + + + + + + + + @* ── Card 3: Gesamtsaldo ── *@ + + = TimeSpan.Zero ? "#10B981" : "#EF4444")};")"> + + + Gleitzeitkonto + + + = TimeSpan.Zero ? "#10B981" : "#EF4444")}; font-size:3rem")" /> + + = TimeSpan.Zero ? "#10B981" : "#EF4444")};")"> + @FormatTs(_totalOvertime, sign: true) + + Gesamtsaldo aller erfassten Tage + + + + + + + + @* ── Säulendiagramm (Tagesverteilung) ── *@ + + + + + Arbeitszeitverteilung diese Woche + Geleistete Nettoarbeitsstunden pro Wochentag + + + +
+ + + + @{ + double plotLeft = 50; + double plotRight = 580; + double plotTop = 30; + double plotBottom = 270; + double plotWidth = plotRight - plotLeft; + double plotHeight = plotBottom - plotTop; + double barWidth = 36; + double spacing = plotWidth / 7; + } + + + @for (int i = 0; i <= 4; i++) + { + double val = (_maxHoursValue / 4.0) * i; + double yPos = plotBottom - (val / _maxHoursValue) * plotHeight; + + @((MarkupString)$"{val:F1}h") + } + + + @for (int i = 0; i < 7; i++) + { + var d = _days[i]; + double xPos = plotLeft + (i * spacing) + (spacing - barWidth) / 2; + double bHeight = (d.WorkedHours / _maxHoursValue) * plotHeight; + double yPos = plotBottom - bHeight; + + string fillCol = "#94A3B8"; // Default light slate + if (d.IsToday) + { + fillCol = "#0EA5E9"; // Sky Blue for today + } + else if (d.WorkedHours >= d.TargetHours && d.TargetHours > 0) + { + fillCol = "#475569"; // Slate Blue for normal completed target + } + else if (d.WorkedHours > 0 && d.TargetHours == 0) + { + fillCol = "#10B981"; // Emerald Green for weekend work (pure overtime) + } + else if (d.WorkedHours > 0) + { + fillCol = "#64748B"; // Slate Medium for incomplete target + } + else + { + fillCol = "#E2E8F0"; // Very light grey for zero hours + } + + + + @d.DayName: @FormatHours(d.WorkedHours) Std. (Soll: @FormatHours(d.TargetHours) Std.) + + + + @if (d.WorkedHours > 0) + { + @((MarkupString)$"{FormatHours(d.WorkedHours)}") + } + + + @((MarkupString)$"{d.DayShortName}") + } + + + @if (_settings.DailyTargetHours > 0) + { + double yTarget = plotBottom - (_settings.DailyTargetHours / _maxHoursValue) * plotHeight; + + @((MarkupString)$"Soll ({_settings.DailyTargetHours} h)") + } + +
+
+
+
+ + @* ── Diagnostics Panel ── *@ + + + + Benutzer-ID: @_userId + Wochensoll: @_weekTargetHours Std. | Netto-Arbeitszeit: @_weekWorkedHours Std. | Wochensaldo: @_weekOvertimeHours Std. + Anzahl erfasste Tage aus DB: @_rawDbDaysCount + + + + + Datum + Tag + Ist (Std) + Soll (Std) + Ist > 0 + Arbeitstag (Einst.) + Aus DB zugeordnet + + + + @foreach (var d in _days) + { + + @d.Date.ToString("yyyy-MM-dd") + @d.DayName + @d.WorkedHours + @d.TargetHours + @(d.WorkedHours > 0 ? "Ja" : "Nein") + @(d.IsWorkDay ? "Ja" : "Nein") + @(_dbMatchStatus.GetValueOrDefault(d.Date, "Nein")) + + } + + + + + +
+
+} + +@code { + private bool _loading = true; + private int _userId; + private AppSettings _settings = new(); + private List _days = []; + private TimeSpan _totalOvertime; + private double _weekWorkedHours; + private double _weekTargetHours; + private double _weekOvertimeHours; + private double _maxHoursValue = 10.0; + + // Debug variables + private int _rawDbDaysCount = 0; + private Dictionary _dbMatchStatus = new(); + + private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE"); + + protected override async Task OnInitializedAsync() + { + 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); + + var today = DateOnly.FromDateTime(DateTime.Today); + var monday = GetMonday(today); + + var dbDays = await TrackerService.GetWeekAsync(_userId, monday); + _rawDbDaysCount = dbDays.Count; + _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings); + + _weekTargetHours = 0; + _weekWorkedHours = 0; + _dbMatchStatus.Clear(); + + _days = Enumerable.Range(0, 7).Select(i => + { + var date = monday.AddDays(i); + bool isWorkDay = _settings.IsWorkDay(date.DayOfWeek); + double target = isWorkDay ? _settings.DailyTargetHours : 0.0; + + if (isWorkDay) + { + _weekTargetHours += _settings.DailyTargetHours; + } + + var wd = dbDays.FirstOrDefault(d => d.Date == date); + double worked = 0.0; + + if (wd != null) + { + _dbMatchStatus[date] = $"Ja (Id={wd.Id}, Start={wd.StartTime}, Ende={wd.EndTime})"; + + if (wd.StartTime != null && wd.EndTime != null) + { + var gross = wd.EndTime.Value.ToTimeSpan() - wd.StartTime.Value.ToTimeSpan(); + if (gross > TimeSpan.Zero) + { + var breakTotal = wd.Breaks + .Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime) + .Aggregate(TimeSpan.Zero, (s, b) => + s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan())); + worked = (gross - breakTotal).TotalHours; + if (worked < 0) worked = 0.0; + } + } + } + else + { + _dbMatchStatus[date] = "Nein"; + } + + _weekWorkedHours += worked; + + return new DayStat + { + Date = date, + DayName = date.ToString("dddd", _deCulture), + DayShortName = date.ToString("ddd", _deCulture), + WorkedHours = worked, + TargetHours = target, + IsToday = date == today, + IsWorkDay = isWorkDay + }; + }).ToList(); + + _weekOvertimeHours = _weekWorkedHours - _weekTargetHours; + + var maxWorked = _days.Max(d => d.WorkedHours); + _maxHoursValue = Math.Max(10.0, Math.Max(maxWorked, _settings.DailyTargetHours)); + + _loading = false; + } + + private static DateOnly GetMonday(DateOnly date) + { + int diff = ((int)date.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7; + return date.AddDays(-diff); + } + + private string FormatHours(double hours) + { + var ts = TimeSpan.FromHours(hours); + return $"{(int)ts.TotalHours}:{ts.Minutes:D2}"; + } + + private string FormatTs(TimeSpan ts, bool sign = false) + { + if (ts == TimeSpan.Zero && sign) return "±0:00"; + var prefix = sign ? (ts >= TimeSpan.Zero ? "+" : "−") : (ts < TimeSpan.Zero ? "−" : ""); + var abs = ts.Duration(); + return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}"; + } + + private sealed class DayStat + { + public DateOnly Date { get; set; } + public string DayName { get; set; } = ""; + public string DayShortName { get; set; } = ""; + public double WorkedHours { get; set; } + public double TargetHours { get; set; } + public bool IsToday { get; set; } + public bool IsWorkDay { get; set; } + } +} diff --git a/timetracker.Client/Components/Pages/UrlaubsMaximizer.razor b/timetracker.Client/Components/Pages/UrlaubsMaximizer.razor index e6e095b..879743b 100644 --- a/timetracker.Client/Components/Pages/UrlaubsMaximizer.razor +++ b/timetracker.Client/Components/Pages/UrlaubsMaximizer.razor @@ -21,7 +21,7 @@ else @* ── Header ── *@ + Style="background: #C2410C; color:white;">