diff --git a/timetracker.Client/Components/Layout/MainLayout.razor b/timetracker.Client/Components/Layout/MainLayout.razor index 2a28816..61f2632 100644 --- a/timetracker.Client/Components/Layout/MainLayout.razor +++ b/timetracker.Client/Components/Layout/MainLayout.razor @@ -2,26 +2,27 @@ @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager Nav @inject IUserNotificationService UserNotificationService +@inject IJSRuntime JSRuntime @implements IDisposable @using System.Security.Claims - + - - - - - Timetracker - + + + - - + + @@ -39,12 +40,39 @@ @code { private bool _drawerOpen = true; + private bool _isDarkMode; protected override void OnInitialized() { UserNotificationService.OnUserDeleted += HandleUserDeleted; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + try + { + var saved = await JSRuntime.InvokeAsync("localStorage.getItem", "darkMode"); + if (!string.IsNullOrEmpty(saved)) + { + _isDarkMode = bool.Parse(saved); + StateHasChanged(); + } + } + catch + { + // Ignored during prerendering/SSR + } + } + } + + private async Task ToggleDarkMode() + { + _isDarkMode = !_isDarkMode; + await JSRuntime.InvokeVoidAsync("localStorage.setItem", "darkMode", _isDarkMode.ToString().ToLower()); + } + private async Task HandleUserDeleted(int deletedUserId) { var state = await AuthStateProvider.GetAuthenticationStateAsync(); @@ -71,10 +99,31 @@ SecondaryDarken = "#0284C7", AppbarBackground = "#0F172A", Background = "#F8FAFC", - DrawerBackground = "#FFFFFF", + DrawerBackground = "#0F172A", + DrawerText = "#94A3B8", + DrawerIcon = "#94A3B8", Surface = "#FFFFFF", TextPrimary = "#0F172A", TextSecondary = "#475569", + }, + PaletteDark = new PaletteDark + { + Primary = "#0EA5E9", + PrimaryDarken = "#0284C7", + PrimaryLighten = "#38BDF8", + Secondary = "#0EA5E9", + SecondaryDarken = "#0284C7", + AppbarBackground = "#0F172A", + AppbarText = "#F8FAFC", + Background = "#0B0F19", + DrawerBackground = "#0F172A", + DrawerText = "#94A3B8", + DrawerIcon = "#94A3B8", + Surface = "#1E293B", + TextPrimary = "#F8FAFC", + TextSecondary = "#94A3B8", + ActionDefault = "#94A3B8", + Divider = "rgba(255, 255, 255, 0.08)", } }; diff --git a/timetracker.Client/Components/Layout/MainLayout.razor.css b/timetracker.Client/Components/Layout/MainLayout.razor.css index 38d1f25..2b25e7b 100644 --- a/timetracker.Client/Components/Layout/MainLayout.razor.css +++ b/timetracker.Client/Components/Layout/MainLayout.razor.css @@ -1,81 +1,39 @@ -.page { - position: relative; - display: flex; - flex-direction: column; +/* Custom scrollbar for the modern drawer */ +::deep .modern-drawer .mud-drawer-content::-webkit-scrollbar { + width: 6px; +} +::deep .modern-drawer .mud-drawer-content::-webkit-scrollbar-track { + background: transparent; +} +::deep .modern-drawer .mud-drawer-content::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; +} +::deep .modern-drawer .mud-drawer-content::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); } -main { - flex: 1; +::deep .modern-drawer { + border-right: 1px solid rgba(255, 255, 255, 0.08) !important; + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1) !important; + transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important; } -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +/* Glassmorphism for the appbar */ +::deep .mud-appbar { + background: rgba(15, 23, 42, 0.85) !important; + backdrop-filter: blur(12px) !important; + -webkit-backdrop-filter: blur(12px) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important; } -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } +/* Smooth transition in main layout content */ +::deep .mud-main-content { + transition: padding-left 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important; + background-color: #F8FAFC; } +/* Blazor Error UI */ #blazor-error-ui { color-scheme: light only; background: lightyellow; @@ -90,9 +48,9 @@ main { z-index: 1000; } - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} diff --git a/timetracker.Client/Components/Layout/NavMenu.razor b/timetracker.Client/Components/Layout/NavMenu.razor index 543c2f5..f5118f8 100644 --- a/timetracker.Client/Components/Layout/NavMenu.razor +++ b/timetracker.Client/Components/Layout/NavMenu.razor @@ -1,41 +1,97 @@ - - - - Wochenübersicht - Monatsübersicht - Statistiken - Feiertage - Urlaubs-Maximizer - Einstellungen - - - - Benutzerverwaltung - - - - - - - @* Username Display - Styled to match NavLink layout exactly *@ - - - @context.User.Identity?.Name - - - + + @* --- Brand / Logo Section --- *@ + + + + + Timetracker + + - + @* --- Navigation Links --- *@ + + + + Wochenübersicht + + + Monatsübersicht + + + Statistiken + + + Feiertage + + + Urlaubs-Maximizer + + + Einstellungen + + + + + + Admin + + + + + Benutzerverwaltung + + + + + + + + @* --- Footer (User Profile & Action Info) --- *@ + \ No newline at end of file + + + + + + Abmelden + + + + + + Version 1.4 + + + + + + +@code { + [Parameter] public EventCallback OnToggleDrawer { get; set; } + + private string GetInitials(string? name) + { + if (string.IsNullOrWhiteSpace(name)) + return "U"; + var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 1) + return parts[0][..Math.Min(2, parts[0].Length)].ToUpper(); + return $"{parts[0][0]}{parts[1][0]}".ToUpper(); + } +} \ No newline at end of file diff --git a/timetracker.Client/Components/Layout/NavMenu.razor.css b/timetracker.Client/Components/Layout/NavMenu.razor.css index a2aeace..0e18fa0 100644 --- a/timetracker.Client/Components/Layout/NavMenu.razor.css +++ b/timetracker.Client/Components/Layout/NavMenu.razor.css @@ -1,105 +1,286 @@ -.navbar-toggler { - appearance: none; - cursor: pointer; - width: 3.5rem; - height: 2.5rem; - color: white; - position: absolute; - top: 0.5rem; - right: 1rem; - border: 1px solid rgba(255, 255, 255, 0.1); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +/* Base container covering full height */ +.nav-container { + display: flex; + flex-direction: column; + height: 100%; + color: #94A3B8; /* Slate 400 */ } -.navbar-toggler:checked { - background-color: rgba(255, 255, 255, 0.5); +/* Ensure tooltips wrapper spans full width of menu */ +::deep .mud-tooltip-root { + display: block !important; + width: 100% !important; } -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); +/* Header with logo and title */ +.nav-header { + height: 64px; + display: flex; + align-items: center; + padding: 0 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + margin-bottom: 8px; + box-sizing: border-box; } -.navbar-brand { +.logo-wrapper { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + transition: all 0.25s ease; +} + +::deep .drawer-toggle-btn { + color: #94A3B8 !important; + padding: 8px !important; + margin-left: -8px !important; + transition: all 0.2s ease !important; +} + +::deep .drawer-toggle-btn:hover { + color: #FFFFFF !important; + background-color: rgba(255, 255, 255, 0.05) !important; +} + +::deep .logo-icon { + color: #0EA5E9 !important; /* Sky 500 */ + font-size: 1.6rem !important; + transition: opacity 0.2s ease; +} + +.brand-text { font-size: 1.1rem; + font-weight: 800; + color: #FFFFFF; + letter-spacing: 0.5px; + white-space: nowrap; + opacity: 1; + transition: opacity 0.2s ease; } -.bi { - display: inline-block; +/* Navigation scrollable content */ +.nav-content { + flex: 1; + overflow-y: auto; + overflow-x: hidden; +} + +/* Divider inside navigation list */ +.admin-divider-container { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 20px 8px 20px; + box-sizing: border-box; + transition: padding 0.25s ease; +} + +.admin-divider-text { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1.5px; + color: #475569; /* Slate 600 */ + white-space: nowrap; +} + +.admin-divider-line { + flex: 1; + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.06); + margin: 0; +} + +/* Standard NavLink customization */ +::deep .custom-nav-link { + margin: 4px 12px !important; + border-radius: 12px !important; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important; + overflow: hidden; +} + +/* Normal (inactive) text and icon colors */ +::deep .custom-nav-link, +::deep .custom-nav-link .mud-nav-link-text, +::deep .custom-nav-link .mud-nav-link-icon-default { + color: #94A3B8 !important; /* Slate 400 */ +} + +/* NavLink hover */ +::deep .custom-nav-link:hover { + background-color: rgba(255, 255, 255, 0.05) !important; +} + +::deep .custom-nav-link:hover, +::deep .custom-nav-link:hover .mud-nav-link-text, +::deep .custom-nav-link:hover .mud-nav-link-icon-default { + color: #F8FAFC !important; /* Slate 50 */ +} + +/* NavLink active state (Fixes active color contrast issue) */ +::deep .custom-nav-link.active { + background: linear-gradient(90deg, rgba(14, 165, 233, 0.15) 0%, rgba(14, 165, 233, 0.04) 100%) !important; + font-weight: 600 !important; + box-shadow: inset 3px 0 0 #0EA5E9 !important; +} + +::deep .custom-nav-link.active, +::deep .custom-nav-link.active .mud-nav-link-text, +::deep .custom-nav-link.active .mud-nav-link-icon-default { + color: #0EA5E9 !important; /* Sky 500 */ +} + +/* Admin link color overrides */ +::deep .admin-link .mud-nav-link-icon-default { + color: rgba(239, 68, 68, 0.7) !important; /* Red 500 */ +} +::deep .admin-link.active .mud-nav-link-icon-default { + color: #EF4444 !important; +} + +/* Footer Section */ +.nav-footer { + border-top: 1px solid rgba(255, 255, 255, 0.06); + padding: 12px 0; + box-sizing: border-box; + display: flex; + flex-direction: column; + background: rgba(15, 23, 42, 0.2); +} + +/* User profile card */ +.user-profile-card { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 20px; + margin-bottom: 8px; + box-sizing: border-box; + transition: all 0.25s ease; +} + +.avatar-container { position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; + display: inline-flex; } -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +::deep .user-avatar { + width: 36px !important; + height: 36px !important; + background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%) !important; + color: #FFFFFF !important; + font-weight: 700 !important; + font-size: 0.875rem !important; + border: 2px solid rgba(255, 255, 255, 0.1) !important; } -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +.status-dot { + position: absolute; + bottom: -1px; + right: -1px; + width: 10px; + height: 10px; + background-color: #10B981; /* Emerald 500 */ + border: 2px solid #0F172A; + border-radius: 50%; } -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +.user-info { + display: flex; + flex-direction: column; + overflow: hidden; + transition: opacity 0.15s ease; } -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; +.user-name { + font-size: 0.85rem; + font-weight: 600; + color: #FFFFFF; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep .nav-link { - color: #d7d7d7; - background: none; - border: none; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - width: 100%; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; +.user-status { + font-size: 0.7rem; + color: #10B981; } -.nav-item ::deep .nav-link:hover { - background-color: rgba(255,255,255,0.1); - color: white; +/* Special links in footer */ +::deep .logout-link .mud-nav-link-icon-default { + color: rgba(148, 163, 184, 0.6) !important; +} +::deep .logout-link:hover .mud-nav-link-icon-default { + color: #EF4444 !important; } -.nav-scrollable { - display: none; +::deep .version-link { + opacity: 0.7; +} +::deep .version-link .mud-nav-link-text { + font-size: 0.75rem !important; } -.navbar-toggler:checked ~ .nav-scrollable { - display: block; +/* --- COLLAPSED DRAWER (MINI STATE) STYLES --- */ + +/* Parent matches the mini drawer element (Note the correct Blazor CSS isolation placement) */ +.mud-drawer--closed ::deep .logo-icon, +.mud-drawer--closed ::deep .brand-text { + display: none !important; + opacity: 0; } -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .nav-scrollable { - /* Never collapse the sidebar for wide screens */ - display: block; - - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } +.mud-drawer--closed ::deep .logo-wrapper { + justify-content: center !important; +} + +.mud-drawer--closed ::deep .drawer-toggle-btn { + margin-left: 0 !important; +} + +.mud-drawer--closed ::deep .admin-divider-container { + padding: 16px 0 8px 0 !important; + justify-content: center !important; +} + +.mud-drawer--closed ::deep .admin-divider-text { + display: none !important; +} + +.mud-drawer--closed ::deep .admin-divider-line { + width: 24px !important; + flex: none !important; +} + +.mud-drawer--closed ::deep .user-profile-card { + justify-content: center !important; + padding: 8px 0 !important; +} + +.mud-drawer--closed ::deep .user-info { + display: none !important; +} + +.mud-drawer--closed ::deep .version-text { + display: none !important; +} + +/* Ensure link text is hidden in mini mode to prevent clunky clipping */ +.mud-drawer--closed ::deep .custom-nav-link .mud-nav-link-text { + display: none !important; +} + +/* Center link icons when mini and adjust spacing */ +.mud-drawer--closed ::deep .custom-nav-link { + margin: 4px 6px !important; + padding: 8px 0 !important; + justify-content: center !important; + min-height: 40px !important; + display: flex !important; + align-items: center !important; +} + +.mud-drawer--closed ::deep .custom-nav-link .mud-nav-link-icon-default { + margin-right: 0 !important; }