Spaces:
Running
Running
User commited on
Commit ·
f6d34ff
1
Parent(s): 8f72634
fix: overall UI
Browse files- TaskTrackingSystem.WebApp/Components/App.razor +67 -55
- TaskTrackingSystem.WebApp/Components/Layout/MainLayout.razor +100 -27
- TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor +7 -6
- TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor.css +50 -67
- TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/Login.razor +12 -9
- TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/Register.razor +13 -12
- TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/ResetPassword.razor +16 -8
- TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/EmployeeDashboard.razor +18 -18
- TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/Home.razor +26 -26
- TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/ManagerDashboard.razor +24 -24
- TaskTrackingSystem.WebApp/Components/Pages/Features/Issues/AddIssue.razor +1 -1
- TaskTrackingSystem.WebApp/Components/Pages/Features/Issues/Issues.razor +1 -1
- TaskTrackingSystem.WebApp/Components/Pages/Features/Projects/ProjectAssign.razor +1 -1
- TaskTrackingSystem.WebApp/Components/Pages/Features/Projects/Projects.razor +3 -3
- TaskTrackingSystem.WebApp/Components/Pages/Features/Roles/Roles.razor +8 -8
- TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/KanbanBoard.razor +2 -2
- TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/TaskAssign.razor +2 -2
- TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/TaskDetails.razor +3 -3
- TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/Tasks.razor +3 -3
- TaskTrackingSystem.WebApp/Components/Pages/Features/Users/Users.razor +2 -2
- TaskTrackingSystem.WebApp/Components/Partial/ConfirmDialog.razor +9 -5
- TaskTrackingSystem.WebApp/Components/Partial/EmptyState.razor +5 -3
- TaskTrackingSystem.WebApp/Components/Partial/LanguageToggle.razor +1 -1
- TaskTrackingSystem.WebApp/Components/Partial/LoadingSpinner.razor +1 -1
- TaskTrackingSystem.WebApp/Components/Partial/NotificationBell.razor +6 -6
- TaskTrackingSystem.WebApp/Components/Partial/Pagination.razor +13 -21
- TaskTrackingSystem.WebApp/Components/Partial/SuccessAlert.razor +2 -2
- TaskTrackingSystem.WebApp/Components/Partial/ThemeToggle.razor +1 -1
- TaskTrackingSystem.WebApp/wwwroot/app.css +470 -29
TaskTrackingSystem.WebApp/Components/App.razor
CHANGED
|
@@ -71,39 +71,40 @@
|
|
| 71 |
};
|
| 72 |
</script>
|
| 73 |
<script>
|
| 74 |
-
const brandDark = "#
|
| 75 |
-
const brandDarkAlt = "#
|
| 76 |
-
const brandPrimary = "#
|
| 77 |
-
const
|
| 78 |
-
const
|
| 79 |
-
const
|
|
|
|
| 80 |
|
| 81 |
const semanticRedBg = "#FFE4E6";
|
| 82 |
-
const semanticRedText = "#
|
| 83 |
|
| 84 |
-
const semanticGreenBg = "#
|
| 85 |
-
const semanticGreenText = "#
|
| 86 |
|
| 87 |
-
const semanticNeutralBg = "#
|
| 88 |
-
const semanticNeutralText = "#
|
| 89 |
|
| 90 |
-
const neutralBg = "#
|
| 91 |
-
const neutralBorder = "#
|
| 92 |
-
const neutralMuted = "#
|
| 93 |
-
const neutralDark = "#
|
| 94 |
|
| 95 |
const customGray = {
|
| 96 |
50: neutralBg,
|
| 97 |
-
100: "#
|
| 98 |
200: neutralBorder,
|
| 99 |
-
300: "#
|
| 100 |
-
400: "#
|
| 101 |
500: neutralMuted,
|
| 102 |
-
600: "#
|
| 103 |
-
700: "#
|
| 104 |
-
800: "#
|
| 105 |
900: neutralDark,
|
| 106 |
-
950:
|
| 107 |
};
|
| 108 |
|
| 109 |
tailwind.config = {
|
|
@@ -206,16 +207,16 @@
|
|
| 206 |
900: "#0C4A6E"
|
| 207 |
},
|
| 208 |
blue: {
|
| 209 |
-
50:
|
| 210 |
-
100:
|
| 211 |
-
200: "#
|
| 212 |
-
300: "#
|
| 213 |
-
400: "#
|
| 214 |
-
500:
|
| 215 |
-
600:
|
| 216 |
-
700:
|
| 217 |
-
800:
|
| 218 |
-
900:
|
| 219 |
},
|
| 220 |
amber: {
|
| 221 |
50: "#FEF3C7",
|
|
@@ -243,49 +244,49 @@
|
|
| 243 |
},
|
| 244 |
violet: {
|
| 245 |
50: brandLight,
|
| 246 |
-
100: "#
|
| 247 |
-
200:
|
| 248 |
-
300:
|
| 249 |
-
400:
|
| 250 |
-
500:
|
| 251 |
600: brandPrimary,
|
| 252 |
-
700:
|
| 253 |
800: brandDark,
|
| 254 |
900: brandDark,
|
| 255 |
950: brandDark
|
| 256 |
},
|
| 257 |
purple: {
|
| 258 |
50: brandLight,
|
| 259 |
-
100: "#
|
| 260 |
-
200:
|
| 261 |
-
300:
|
| 262 |
-
400:
|
| 263 |
-
500:
|
| 264 |
600: brandPrimary,
|
| 265 |
-
700:
|
| 266 |
800: brandDark,
|
| 267 |
900: brandDark,
|
| 268 |
950: brandDark
|
| 269 |
},
|
| 270 |
indigo: {
|
| 271 |
50: brandLight,
|
| 272 |
-
100: "#
|
| 273 |
-
200:
|
| 274 |
-
300:
|
| 275 |
-
400:
|
| 276 |
-
500:
|
| 277 |
600: brandPrimary,
|
| 278 |
-
700:
|
| 279 |
800: brandDark,
|
| 280 |
900: brandDark,
|
| 281 |
950: brandDark
|
| 282 |
},
|
| 283 |
|
| 284 |
chart: {
|
| 285 |
-
purple:
|
| 286 |
-
teal: "#
|
| 287 |
green: "#10B981",
|
| 288 |
-
neutral:
|
| 289 |
},
|
| 290 |
|
| 291 |
background: neutralBg,
|
|
@@ -308,6 +309,17 @@
|
|
| 308 |
"accent-foreground": "#FFFFFF",
|
| 309 |
|
| 310 |
border: neutralBorder,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
}
|
| 312 |
}
|
| 313 |
}
|
|
@@ -457,13 +469,13 @@
|
|
| 457 |
}
|
| 458 |
},
|
| 459 |
currentDateIndicator: {
|
| 460 |
-
color: '#
|
| 461 |
dashStyle: 'ShortDash',
|
| 462 |
width: 2,
|
| 463 |
label: {
|
| 464 |
format: 'Today',
|
| 465 |
style: {
|
| 466 |
-
color: '#
|
| 467 |
fontWeight: 'bold'
|
| 468 |
}
|
| 469 |
}
|
|
|
|
| 71 |
};
|
| 72 |
</script>
|
| 73 |
<script>
|
| 74 |
+
const brandDark = "#172554";
|
| 75 |
+
const brandDarkAlt = "#1E3A8A";
|
| 76 |
+
const brandPrimary = "#2563EB";
|
| 77 |
+
const brandPrimaryHover = "#1D4ED8";
|
| 78 |
+
const brandAccent = "#0F766E";
|
| 79 |
+
const brandLight = "#EFF6FF";
|
| 80 |
+
const brandMuted = "#64748B";
|
| 81 |
|
| 82 |
const semanticRedBg = "#FFE4E6";
|
| 83 |
+
const semanticRedText = "#BE123C";
|
| 84 |
|
| 85 |
+
const semanticGreenBg = "#CCFBF1";
|
| 86 |
+
const semanticGreenText = "#0F766E";
|
| 87 |
|
| 88 |
+
const semanticNeutralBg = "#F8FAFC";
|
| 89 |
+
const semanticNeutralText = "#475569";
|
| 90 |
|
| 91 |
+
const neutralBg = "#F6F8FB";
|
| 92 |
+
const neutralBorder = "#E2E8F0";
|
| 93 |
+
const neutralMuted = "#64748B";
|
| 94 |
+
const neutralDark = "#0F172A";
|
| 95 |
|
| 96 |
const customGray = {
|
| 97 |
50: neutralBg,
|
| 98 |
+
100: "#F1F5F9",
|
| 99 |
200: neutralBorder,
|
| 100 |
+
300: "#CBD5E1",
|
| 101 |
+
400: "#94A3B8",
|
| 102 |
500: neutralMuted,
|
| 103 |
+
600: "#475569",
|
| 104 |
+
700: "#334155",
|
| 105 |
+
800: "#1E293B",
|
| 106 |
900: neutralDark,
|
| 107 |
+
950: "#020617"
|
| 108 |
};
|
| 109 |
|
| 110 |
tailwind.config = {
|
|
|
|
| 207 |
900: "#0C4A6E"
|
| 208 |
},
|
| 209 |
blue: {
|
| 210 |
+
50: "#EFF6FF",
|
| 211 |
+
100: "#DBEAFE",
|
| 212 |
+
200: "#BFDBFE",
|
| 213 |
+
300: "#93C5FD",
|
| 214 |
+
400: "#60A5FA",
|
| 215 |
+
500: "#3B82F6",
|
| 216 |
+
600: brandPrimary,
|
| 217 |
+
700: brandPrimaryHover,
|
| 218 |
+
800: brandDarkAlt,
|
| 219 |
+
900: brandDark
|
| 220 |
},
|
| 221 |
amber: {
|
| 222 |
50: "#FEF3C7",
|
|
|
|
| 244 |
},
|
| 245 |
violet: {
|
| 246 |
50: brandLight,
|
| 247 |
+
100: "#DBEAFE",
|
| 248 |
+
200: "#BFDBFE",
|
| 249 |
+
300: "#93C5FD",
|
| 250 |
+
400: "#60A5FA",
|
| 251 |
+
500: "#3B82F6",
|
| 252 |
600: brandPrimary,
|
| 253 |
+
700: brandPrimaryHover,
|
| 254 |
800: brandDark,
|
| 255 |
900: brandDark,
|
| 256 |
950: brandDark
|
| 257 |
},
|
| 258 |
purple: {
|
| 259 |
50: brandLight,
|
| 260 |
+
100: "#DBEAFE",
|
| 261 |
+
200: "#BFDBFE",
|
| 262 |
+
300: "#93C5FD",
|
| 263 |
+
400: "#60A5FA",
|
| 264 |
+
500: "#3B82F6",
|
| 265 |
600: brandPrimary,
|
| 266 |
+
700: brandPrimaryHover,
|
| 267 |
800: brandDark,
|
| 268 |
900: brandDark,
|
| 269 |
950: brandDark
|
| 270 |
},
|
| 271 |
indigo: {
|
| 272 |
50: brandLight,
|
| 273 |
+
100: "#DBEAFE",
|
| 274 |
+
200: "#BFDBFE",
|
| 275 |
+
300: "#93C5FD",
|
| 276 |
+
400: "#60A5FA",
|
| 277 |
+
500: "#3B82F6",
|
| 278 |
600: brandPrimary,
|
| 279 |
+
700: brandPrimaryHover,
|
| 280 |
800: brandDark,
|
| 281 |
900: brandDark,
|
| 282 |
950: brandDark
|
| 283 |
},
|
| 284 |
|
| 285 |
chart: {
|
| 286 |
+
purple: brandPrimary,
|
| 287 |
+
teal: "#14B8A6",
|
| 288 |
green: "#10B981",
|
| 289 |
+
neutral: neutralBorder
|
| 290 |
},
|
| 291 |
|
| 292 |
background: neutralBg,
|
|
|
|
| 309 |
"accent-foreground": "#FFFFFF",
|
| 310 |
|
| 311 |
border: neutralBorder,
|
| 312 |
+
},
|
| 313 |
+
borderRadius: {
|
| 314 |
+
none: "0",
|
| 315 |
+
sm: "0.375rem",
|
| 316 |
+
DEFAULT: "0.5rem",
|
| 317 |
+
md: "0.5rem",
|
| 318 |
+
lg: "0.5rem",
|
| 319 |
+
xl: "0.5rem",
|
| 320 |
+
"2xl": "0.5rem",
|
| 321 |
+
"3xl": "0.5rem",
|
| 322 |
+
full: "9999px"
|
| 323 |
}
|
| 324 |
}
|
| 325 |
}
|
|
|
|
| 469 |
}
|
| 470 |
},
|
| 471 |
currentDateIndicator: {
|
| 472 |
+
color: '#2563eb',
|
| 473 |
dashStyle: 'ShortDash',
|
| 474 |
width: 2,
|
| 475 |
label: {
|
| 476 |
format: 'Today',
|
| 477 |
style: {
|
| 478 |
+
color: '#2563eb',
|
| 479 |
fontWeight: 'bold'
|
| 480 |
}
|
| 481 |
}
|
TaskTrackingSystem.WebApp/Components/Layout/MainLayout.razor
CHANGED
|
@@ -3,29 +3,43 @@
|
|
| 3 |
@using System.Security.Claims
|
| 4 |
@inject TaskTrackingSystem.WebApp.Localization.UiLanguageService Language
|
| 5 |
|
| 6 |
-
<div class="
|
| 7 |
-
<
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
<i data-lucide="flame" class="h-5 w-5 fill-current"></i>
|
| 11 |
</span>
|
| 12 |
-
<span class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</div>
|
| 14 |
|
| 15 |
<NavMenu @rendermode="new InteractiveServerRenderMode(prerender: false)" />
|
| 16 |
|
| 17 |
-
<div class="border-t border-
|
| 18 |
<AuthorizeView>
|
| 19 |
<Authorized>
|
| 20 |
-
<div class="flex items-center rounded-
|
| 21 |
-
<span class="flex h-10 w-10 items-center justify-center rounded-full bg-
|
| 22 |
@(context.User.Identity?.Name?.Length > 0 ? context.User.Identity.Name[0].ToString().ToUpper() : "U")
|
| 23 |
</span>
|
| 24 |
<div class="ml-3 overflow-hidden flex flex-col">
|
| 25 |
<span class="text-sm font-semibold truncate max-w-[130px] text-white">@context.User.Identity?.Name</span>
|
| 26 |
<form id="logout-form" method="post" action="/account/logout" class="mt-0.5">
|
| 27 |
<AntiforgeryToken />
|
| 28 |
-
<button type="button" onclick="document.getElementById('logout-confirm-dialog').style.display='flex';" class="text-xs hover:opacity-
|
| 29 |
<i data-lucide="log-out" class="h-3 w-3"></i>
|
| 30 |
@Language.Texts.SignOut
|
| 31 |
</button>
|
|
@@ -34,7 +48,7 @@
|
|
| 34 |
</div>
|
| 35 |
</Authorized>
|
| 36 |
<NotAuthorized>
|
| 37 |
-
<a href="/login" class="flex items-center justify-center rounded-
|
| 38 |
@Language.Texts.SignIn
|
| 39 |
</a>
|
| 40 |
</NotAuthorized>
|
|
@@ -42,9 +56,9 @@
|
|
| 42 |
</div>
|
| 43 |
|
| 44 |
<div id="logout-confirm-dialog" class="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm" style="display: none;">
|
| 45 |
-
<div class="rounded-
|
| 46 |
<div class="p-6 text-center">
|
| 47 |
-
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full mb-4" style="background-color: var(--status-warning-bg); color: var(--
|
| 48 |
<i data-lucide="log-out" class="h-7 w-7"></i>
|
| 49 |
</div>
|
| 50 |
<h3 class="text-lg font-semibold text-slate-900">@Language.Texts.SignOutTitle</h3>
|
|
@@ -62,10 +76,21 @@
|
|
| 62 |
</div>
|
| 63 |
</aside>
|
| 64 |
|
| 65 |
-
<main class="flex-
|
| 66 |
-
<header class="flex h-16 items-center justify-between border-b border-slate-200 bg-white px-
|
| 67 |
-
<
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
<AuthorizeView>
|
| 70 |
<Authorized>
|
| 71 |
<FirebaseTokenSync />
|
|
@@ -77,7 +102,7 @@
|
|
| 77 |
</div>
|
| 78 |
</header>
|
| 79 |
|
| 80 |
-
<div class="
|
| 81 |
@if (isAuthorizedPage)
|
| 82 |
{
|
| 83 |
@Body
|
|
@@ -115,17 +140,65 @@
|
|
| 115 |
string.IsNullOrEmpty(Navigation.ToBaseRelativePath(Navigation.Uri)) ||
|
| 116 |
Navigation.ToBaseRelativePath(Navigation.Uri).Split('?')[0].Trim('/') is "dashboard" or "Home" or "";
|
| 117 |
|
| 118 |
-
private string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
{
|
| 120 |
-
"
|
| 121 |
-
"
|
| 122 |
-
"
|
| 123 |
-
"
|
| 124 |
-
"
|
| 125 |
-
"
|
| 126 |
-
"
|
| 127 |
-
"
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
};
|
| 130 |
|
| 131 |
protected override async Task OnInitializedAsync()
|
|
|
|
| 3 |
@using System.Security.Claims
|
| 4 |
@inject TaskTrackingSystem.WebApp.Localization.UiLanguageService Language
|
| 5 |
|
| 6 |
+
<div class="app-shell min-h-screen bg-slate-50 text-slate-950 lg:flex lg:overflow-hidden">
|
| 7 |
+
<button type="button"
|
| 8 |
+
class="sidebar-scrim"
|
| 9 |
+
onclick="document.body.classList.remove('tts-sidebar-open');"
|
| 10 |
+
aria-label="Close navigation"></button>
|
| 11 |
+
|
| 12 |
+
<aside class="app-sidebar fixed inset-y-0 left-0 z-[60] flex w-72 -translate-x-full flex-col overflow-hidden border-r border-slate-800 bg-slate-950 text-white transition-transform duration-200 lg:static lg:z-auto lg:min-h-screen lg:translate-x-0 lg:shrink-0">
|
| 13 |
+
<div class="app-sidebar-brand flex min-h-20 items-center gap-3 px-5 py-4">
|
| 14 |
+
<span class="app-sidebar-logo inline-flex h-10 w-10 items-center justify-center rounded-lg bg-blue-600 text-white shadow-sm">
|
| 15 |
<i data-lucide="flame" class="h-5 w-5 fill-current"></i>
|
| 16 |
</span>
|
| 17 |
+
<span class="app-sidebar-title flex flex-col leading-tight">
|
| 18 |
+
<strong class="text-lg font-bold tracking-normal text-white">Taskify</strong>
|
| 19 |
+
<span class="mt-0.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400">Workspace</span>
|
| 20 |
+
</span>
|
| 21 |
+
<button type="button"
|
| 22 |
+
class="control-button mobile-sidebar-close ml-auto inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/10 text-white hover:bg-white/15 hover:text-white lg:hidden"
|
| 23 |
+
onclick="document.body.classList.remove('tts-sidebar-open');"
|
| 24 |
+
aria-label="Close navigation">
|
| 25 |
+
<i data-lucide="x" class="h-4 w-4"></i>
|
| 26 |
+
</button>
|
| 27 |
</div>
|
| 28 |
|
| 29 |
<NavMenu @rendermode="new InteractiveServerRenderMode(prerender: false)" />
|
| 30 |
|
| 31 |
+
<div class="app-sidebar-user mt-auto shrink-0 border-t border-white/10 p-3">
|
| 32 |
<AuthorizeView>
|
| 33 |
<Authorized>
|
| 34 |
+
<div class="flex items-center rounded-lg border border-white/10 bg-white/[0.04] p-2 transition-colors hover:bg-white/[0.08]">
|
| 35 |
+
<span class="flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white font-semibold text-sm shrink-0">
|
| 36 |
@(context.User.Identity?.Name?.Length > 0 ? context.User.Identity.Name[0].ToString().ToUpper() : "U")
|
| 37 |
</span>
|
| 38 |
<div class="ml-3 overflow-hidden flex flex-col">
|
| 39 |
<span class="text-sm font-semibold truncate max-w-[130px] text-white">@context.User.Identity?.Name</span>
|
| 40 |
<form id="logout-form" method="post" action="/account/logout" class="mt-0.5">
|
| 41 |
<AntiforgeryToken />
|
| 42 |
+
<button type="button" onclick="document.getElementById('logout-confirm-dialog').style.display='flex';" class="text-xs hover:opacity-90 font-semibold text-left flex items-center gap-1 transition-all text-slate-300">
|
| 43 |
<i data-lucide="log-out" class="h-3 w-3"></i>
|
| 44 |
@Language.Texts.SignOut
|
| 45 |
</button>
|
|
|
|
| 48 |
</div>
|
| 49 |
</Authorized>
|
| 50 |
<NotAuthorized>
|
| 51 |
+
<a href="/login" class="flex items-center justify-center rounded-lg bg-white/10 text-white px-4 py-2.5 text-sm font-medium hover:bg-white/15 transition-colors">
|
| 52 |
@Language.Texts.SignIn
|
| 53 |
</a>
|
| 54 |
</NotAuthorized>
|
|
|
|
| 56 |
</div>
|
| 57 |
|
| 58 |
<div id="logout-confirm-dialog" class="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm" style="display: none;">
|
| 59 |
+
<div class="rounded-lg shadow-lg w-full max-w-sm mx-4 overflow-hidden animate-in border border-slate-100 bg-white">
|
| 60 |
<div class="p-6 text-center">
|
| 61 |
+
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full mb-4" style="background-color: var(--status-warning-bg); color: var(--status-warning-text);">
|
| 62 |
<i data-lucide="log-out" class="h-7 w-7"></i>
|
| 63 |
</div>
|
| 64 |
<h3 class="text-lg font-semibold text-slate-900">@Language.Texts.SignOutTitle</h3>
|
|
|
|
| 76 |
</div>
|
| 77 |
</aside>
|
| 78 |
|
| 79 |
+
<main class="app-main flex min-w-0 flex-1 flex-col lg:min-h-screen">
|
| 80 |
+
<header class="app-topbar flex min-h-16 items-center justify-between gap-3 border-b border-slate-200 bg-white/95 px-4 backdrop-blur lg:px-6">
|
| 81 |
+
<div class="flex min-w-0 items-center gap-3">
|
| 82 |
+
<button type="button"
|
| 83 |
+
class="control-button mobile-sidebar-toggle inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-600 shadow-sm hover:bg-slate-50 hover:text-blue-600 lg:hidden"
|
| 84 |
+
onclick="document.body.classList.add('tts-sidebar-open');"
|
| 85 |
+
aria-label="Open navigation">
|
| 86 |
+
<i data-lucide="menu" class="h-4 w-4"></i>
|
| 87 |
+
</button>
|
| 88 |
+
<div class="app-topbar-title min-w-0">
|
| 89 |
+
<h1 class="truncate text-base font-bold tracking-normal text-slate-950">@CurrentPageTitle</h1>
|
| 90 |
+
<p class="hidden truncate text-xs font-medium text-slate-500 sm:block">@CurrentPageDescription</p>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
<div class="flex shrink-0 items-center gap-2 sm:gap-3">
|
| 94 |
<AuthorizeView>
|
| 95 |
<Authorized>
|
| 96 |
<FirebaseTokenSync />
|
|
|
|
| 102 |
</div>
|
| 103 |
</header>
|
| 104 |
|
| 105 |
+
<div class="app-content flex-1 overflow-y-auto p-4 lg:p-8">
|
| 106 |
@if (isAuthorizedPage)
|
| 107 |
{
|
| 108 |
@Body
|
|
|
|
| 140 |
string.IsNullOrEmpty(Navigation.ToBaseRelativePath(Navigation.Uri)) ||
|
| 141 |
Navigation.ToBaseRelativePath(Navigation.Uri).Split('?')[0].Trim('/') is "dashboard" or "Home" or "";
|
| 142 |
|
| 143 |
+
private string CurrentRoute => Navigation.ToBaseRelativePath(Navigation.Uri).Split('?')[0].Trim('/');
|
| 144 |
+
|
| 145 |
+
private string CurrentPageTitle
|
| 146 |
+
{
|
| 147 |
+
get
|
| 148 |
+
{
|
| 149 |
+
if (CurrentRoute.StartsWith("projects/", StringComparison.OrdinalIgnoreCase) &&
|
| 150 |
+
CurrentRoute.EndsWith("/tasks", StringComparison.OrdinalIgnoreCase))
|
| 151 |
+
{
|
| 152 |
+
return "Kanban Board";
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
if (CurrentRoute.StartsWith("tasks/", StringComparison.OrdinalIgnoreCase))
|
| 156 |
+
{
|
| 157 |
+
return "Task Details";
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
return CurrentRoute switch
|
| 161 |
+
{
|
| 162 |
+
"" or "dashboard" or "Home" => "Dashboard",
|
| 163 |
+
"dashboard/manager" => "Manager Dashboard",
|
| 164 |
+
"dashboard/employee" => "Employee Dashboard",
|
| 165 |
+
"projects" or "projects/all" => "Projects",
|
| 166 |
+
"projects/assign" => "Project Assignment",
|
| 167 |
+
"tasks" or "tasks/all" => "Tasks",
|
| 168 |
+
"board" => "Kanban Board",
|
| 169 |
+
"tasks/assign" => "Task Assignment",
|
| 170 |
+
"issues" or "issues/all" => "Issues",
|
| 171 |
+
"issues/add" => "Add Issue",
|
| 172 |
+
"reports/tasks" => "Task Report",
|
| 173 |
+
"reports/time" => "Time Tracking",
|
| 174 |
+
"reports/projects" => "Project Progress",
|
| 175 |
+
"reports/overdue" => "Overdue Tasks",
|
| 176 |
+
"reports/employees" => "Employee Report",
|
| 177 |
+
"roles" => "Roles & Access",
|
| 178 |
+
"users" => "Users",
|
| 179 |
+
"audit-logs" => "Audit Logs",
|
| 180 |
+
_ => Language.Texts.Workspace
|
| 181 |
+
};
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
private string CurrentPageDescription => CurrentPageTitle switch
|
| 186 |
{
|
| 187 |
+
"Dashboard" => "A quick overview of work, risk, and progress.",
|
| 188 |
+
"Manager Dashboard" => "Track projects, people, and delivery health.",
|
| 189 |
+
"Employee Dashboard" => "Focus on your tasks, due dates, and progress.",
|
| 190 |
+
"Projects" => "Manage project plans, timelines, and assignments.",
|
| 191 |
+
"Project Assignment" => "Assign team members to the right projects.",
|
| 192 |
+
"Tasks" => "Review, filter, and update work items.",
|
| 193 |
+
"Kanban Board" => "Move work through status columns.",
|
| 194 |
+
"Task Assignment" => "Assign tasks to project members.",
|
| 195 |
+
"Issues" => "Track blockers, notes, and daily work progress.",
|
| 196 |
+
"Add Issue" => "Record a new issue against a task.",
|
| 197 |
+
"Roles & Access" => "Control user roles, menus, and permissions.",
|
| 198 |
+
"Users" => "Manage people, status, and account details.",
|
| 199 |
+
"Audit Logs" => "Review system activity and changes.",
|
| 200 |
+
_ when CurrentPageTitle.Contains("Report", StringComparison.OrdinalIgnoreCase) => "Analyze work data and export reports.",
|
| 201 |
+
_ => "Workspace"
|
| 202 |
};
|
| 203 |
|
| 204 |
protected override async Task OnInitializedAsync()
|
TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor
CHANGED
|
@@ -10,14 +10,15 @@
|
|
| 10 |
@inject NavigationManager Navigation
|
| 11 |
@inject IJSRuntime JS
|
| 12 |
|
| 13 |
-
<nav class="flex-1 space-y-1 px-
|
|
|
|
| 14 |
@if (menus.Any())
|
| 15 |
{
|
| 16 |
@foreach (var menu in menus)
|
| 17 |
{
|
| 18 |
if (menu.SubMenus != null && menu.SubMenus.Count > 0)
|
| 19 |
{
|
| 20 |
-
<button class="nav-dropdown-toggle @(IsMenuGroupExpanded(menu.MenuCode) ? "expanded" : "")"
|
| 21 |
@onclick="() => ToggleMenuGroup(menu.MenuCode)">
|
| 22 |
@RenderIcon(menu.Icon)
|
| 23 |
<span class="flex-1 text-left">@menu.MenuName</span>
|
|
@@ -28,7 +29,7 @@
|
|
| 28 |
<div class="nav-dropdown-children">
|
| 29 |
@foreach (var sub in menu.SubMenus)
|
| 30 |
{
|
| 31 |
-
<NavLink class="nav-link-item nav-child-item"
|
| 32 |
href="@MenuAuthorizationService.NormalizeMenuHref(sub.MenuUrl)"
|
| 33 |
Match="@GetNavLinkMatch(sub.MenuUrl)">
|
| 34 |
@RenderSmallIcon(sub.Icon)
|
|
@@ -40,7 +41,7 @@
|
|
| 40 |
}
|
| 41 |
else
|
| 42 |
{
|
| 43 |
-
<NavLink class="nav-link-item"
|
| 44 |
href="@MenuAuthorizationService.NormalizeMenuHref(menu.MenuUrl)"
|
| 45 |
Match="@(string.IsNullOrWhiteSpace(menu.MenuUrl) || menu.MenuUrl.Trim('/') is "dashboard" or "" ? NavLinkMatch.All : NavLinkMatch.Prefix)">
|
| 46 |
@RenderIcon(menu.Icon)
|
|
@@ -50,7 +51,7 @@
|
|
| 50 |
}
|
| 51 |
@if (isAdmin)
|
| 52 |
{
|
| 53 |
-
<NavLink class="nav-link-item mt-2
|
| 54 |
<i data-lucide="file-text" class="h-4 w-4 mr-3 text-slate-400 shrink-0"></i>
|
| 55 |
<span>@Language.Texts.AuditLogs</span>
|
| 56 |
</NavLink>
|
|
@@ -157,7 +158,7 @@
|
|
| 157 |
}
|
| 158 |
else
|
| 159 |
{
|
| 160 |
-
// Access items are the same
|
| 161 |
foreach (var menu in menus)
|
| 162 |
{
|
| 163 |
if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus))
|
|
|
|
| 10 |
@inject NavigationManager Navigation
|
| 11 |
@inject IJSRuntime JS
|
| 12 |
|
| 13 |
+
<nav class="flex-1 space-y-1 overflow-y-auto px-3 pb-5 pt-2 text-slate-300"
|
| 14 |
+
onclick="if (event.target.closest('a')) document.body.classList.remove('tts-sidebar-open');">
|
| 15 |
@if (menus.Any())
|
| 16 |
{
|
| 17 |
@foreach (var menu in menus)
|
| 18 |
{
|
| 19 |
if (menu.SubMenus != null && menu.SubMenus.Count > 0)
|
| 20 |
{
|
| 21 |
+
<button class="nav-dropdown-toggle flex w-full items-center rounded-lg px-3 py-2.5 text-sm font-semibold text-slate-300 transition-colors hover:bg-white/10 hover:text-white @(IsMenuGroupExpanded(menu.MenuCode) ? "expanded bg-white/10 text-white" : "")"
|
| 22 |
@onclick="() => ToggleMenuGroup(menu.MenuCode)">
|
| 23 |
@RenderIcon(menu.Icon)
|
| 24 |
<span class="flex-1 text-left">@menu.MenuName</span>
|
|
|
|
| 29 |
<div class="nav-dropdown-children">
|
| 30 |
@foreach (var sub in menu.SubMenus)
|
| 31 |
{
|
| 32 |
+
<NavLink class="nav-link-item nav-child-item flex items-center rounded-lg px-3 py-2 text-sm font-semibold text-slate-300 transition-colors hover:bg-white/10 hover:text-white"
|
| 33 |
href="@MenuAuthorizationService.NormalizeMenuHref(sub.MenuUrl)"
|
| 34 |
Match="@GetNavLinkMatch(sub.MenuUrl)">
|
| 35 |
@RenderSmallIcon(sub.Icon)
|
|
|
|
| 41 |
}
|
| 42 |
else
|
| 43 |
{
|
| 44 |
+
<NavLink class="nav-link-item flex items-center rounded-lg px-3 py-2.5 text-sm font-semibold text-slate-300 transition-colors hover:bg-white/10 hover:text-white"
|
| 45 |
href="@MenuAuthorizationService.NormalizeMenuHref(menu.MenuUrl)"
|
| 46 |
Match="@(string.IsNullOrWhiteSpace(menu.MenuUrl) || menu.MenuUrl.Trim('/') is "dashboard" or "" ? NavLinkMatch.All : NavLinkMatch.Prefix)">
|
| 47 |
@RenderIcon(menu.Icon)
|
|
|
|
| 51 |
}
|
| 52 |
@if (isAdmin)
|
| 53 |
{
|
| 54 |
+
<NavLink class="nav-link-item mt-2 flex items-center rounded-lg border-t border-white/10 px-3 py-2.5 pt-3 text-sm font-semibold text-slate-300 transition-colors hover:bg-white/10 hover:text-white" href="/audit-logs" Match="NavLinkMatch.All">
|
| 55 |
<i data-lucide="file-text" class="h-4 w-4 mr-3 text-slate-400 shrink-0"></i>
|
| 56 |
<span>@Language.Texts.AuditLogs</span>
|
| 57 |
</NavLink>
|
|
|
|
| 158 |
}
|
| 159 |
else
|
| 160 |
{
|
| 161 |
+
// Access items are the same - just refresh expanded state for the new route.
|
| 162 |
foreach (var menu in menus)
|
| 163 |
{
|
| 164 |
if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus))
|
TaskTrackingSystem.WebApp/Components/Layout/NavMenu.razor.css
CHANGED
|
@@ -1,99 +1,82 @@
|
|
| 1 |
-
|
| 2 |
-
::deep
|
| 3 |
display: flex;
|
|
|
|
| 4 |
align-items: center;
|
| 5 |
-
border
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
::deep a.nav-link-item.active {
|
| 20 |
-
background-color: rgba(255, 255, 255, 0.15) !important;
|
| 21 |
-
color: #FFFFFF !important;
|
| 22 |
-
border-left: 3px solid #FFFFFF !important;
|
| 23 |
-
font-weight: 600;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
::deep a.nav-link-item.active i {
|
| 27 |
-
color: #FFFFFF !important;
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
::deep a.nav-link-item i {
|
| 31 |
-
color: #CBD5E1;
|
| 32 |
-
transition: color 0.2s ease;
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
::deep a.nav-link-item:hover i {
|
| 36 |
-
color: #FFFFFF;
|
| 37 |
}
|
| 38 |
|
| 39 |
-
/* Dropdown toggle button (parent item) */
|
| 40 |
::deep button.nav-dropdown-toggle {
|
| 41 |
-
|
| 42 |
-
align-items: center;
|
| 43 |
-
width: 100%;
|
| 44 |
-
border-radius: 0.75rem;
|
| 45 |
-
padding: 0.625rem 1rem;
|
| 46 |
-
font-size: 0.875rem;
|
| 47 |
-
font-weight: 500;
|
| 48 |
-
color: #CBD5E1;
|
| 49 |
-
background: none;
|
| 50 |
-
border: none;
|
| 51 |
-
border-left: 3px solid transparent;
|
| 52 |
cursor: pointer;
|
| 53 |
-
transition: all 0.2s ease;
|
| 54 |
}
|
| 55 |
|
|
|
|
| 56 |
::deep button.nav-dropdown-toggle:hover {
|
| 57 |
-
|
|
|
|
| 58 |
color: #FFFFFF;
|
| 59 |
}
|
| 60 |
|
| 61 |
-
::deep
|
| 62 |
-
color:
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
|
| 66 |
-
::deep button.nav-dropdown-toggle
|
| 67 |
-
::deep button.nav-dropdown-toggle.expanded i {
|
| 68 |
color: #FFFFFF;
|
| 69 |
}
|
| 70 |
|
|
|
|
| 71 |
::deep button.nav-dropdown-toggle i {
|
| 72 |
-
color:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
|
| 75 |
-
/* Chevron rotation on expand */
|
| 76 |
::deep button.nav-dropdown-toggle .nav-chevron {
|
| 77 |
-
|
|
|
|
|
|
|
| 78 |
}
|
| 79 |
|
| 80 |
::deep button.nav-dropdown-toggle.expanded .nav-chevron {
|
| 81 |
transform: rotate(180deg);
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
-
/* Dropdown children container */
|
| 85 |
::deep .nav-dropdown-children {
|
| 86 |
-
margin
|
| 87 |
-
padding-left: 0.
|
| 88 |
-
border-left:
|
| 89 |
-
margin-top: 0.125rem;
|
| 90 |
-
margin-bottom: 0.25rem;
|
| 91 |
}
|
| 92 |
|
| 93 |
-
/* Child nav items — slightly smaller */
|
| 94 |
::deep a.nav-child-item {
|
| 95 |
-
padding: 0.
|
| 96 |
-
font-size: 0.
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
|
|
|
| 1 |
+
::deep a.nav-link-item,
|
| 2 |
+
::deep button.nav-dropdown-toggle {
|
| 3 |
display: flex;
|
| 4 |
+
width: 100%;
|
| 5 |
align-items: center;
|
| 6 |
+
border: 1px solid transparent;
|
| 7 |
+
border-radius: var(--radius-lg, 0.5rem);
|
| 8 |
+
padding: 0.65rem 0.85rem;
|
| 9 |
+
color: var(--sidebar-muted, #CBD5E1);
|
| 10 |
+
font-size: 0.9rem;
|
| 11 |
+
font-weight: 650;
|
| 12 |
+
line-height: 1.2;
|
| 13 |
+
transition:
|
| 14 |
+
background-color 0.16s ease,
|
| 15 |
+
border-color 0.16s ease,
|
| 16 |
+
color 0.16s ease,
|
| 17 |
+
transform 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
| 19 |
|
|
|
|
| 20 |
::deep button.nav-dropdown-toggle {
|
| 21 |
+
background: transparent;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
cursor: pointer;
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
+
::deep a.nav-link-item:hover,
|
| 26 |
::deep button.nav-dropdown-toggle:hover {
|
| 27 |
+
border-color: rgba(255, 255, 255, 0.08);
|
| 28 |
+
background-color: rgba(255, 255, 255, 0.07);
|
| 29 |
color: #FFFFFF;
|
| 30 |
}
|
| 31 |
|
| 32 |
+
::deep a.nav-link-item.active {
|
| 33 |
+
border-color: rgba(96, 165, 250, 0.28) !important;
|
| 34 |
+
background-color: var(--sidebar-active, rgba(37, 99, 235, 0.18)) !important;
|
| 35 |
+
color: #FFFFFF !important;
|
| 36 |
+
box-shadow: inset 3px 0 0 var(--brand-primary);
|
| 37 |
}
|
| 38 |
|
| 39 |
+
::deep button.nav-dropdown-toggle.expanded {
|
|
|
|
| 40 |
color: #FFFFFF;
|
| 41 |
}
|
| 42 |
|
| 43 |
+
::deep a.nav-link-item i,
|
| 44 |
::deep button.nav-dropdown-toggle i {
|
| 45 |
+
color: currentColor !important;
|
| 46 |
+
opacity: 0.78;
|
| 47 |
+
transition: opacity 0.16s ease;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
::deep a.nav-link-item:hover i,
|
| 51 |
+
::deep a.nav-link-item.active i,
|
| 52 |
+
::deep button.nav-dropdown-toggle:hover i,
|
| 53 |
+
::deep button.nav-dropdown-toggle.expanded i {
|
| 54 |
+
opacity: 1;
|
| 55 |
}
|
| 56 |
|
|
|
|
| 57 |
::deep button.nav-dropdown-toggle .nav-chevron {
|
| 58 |
+
margin-left: auto;
|
| 59 |
+
opacity: 0.65;
|
| 60 |
+
transition: transform 0.18s ease, opacity 0.18s ease;
|
| 61 |
}
|
| 62 |
|
| 63 |
::deep button.nav-dropdown-toggle.expanded .nav-chevron {
|
| 64 |
transform: rotate(180deg);
|
| 65 |
+
opacity: 1;
|
| 66 |
}
|
| 67 |
|
|
|
|
| 68 |
::deep .nav-dropdown-children {
|
| 69 |
+
margin: 0.2rem 0 0.45rem 1rem;
|
| 70 |
+
padding-left: 0.65rem;
|
| 71 |
+
border-left: 1px solid rgba(148, 163, 184, 0.22);
|
|
|
|
|
|
|
| 72 |
}
|
| 73 |
|
|
|
|
| 74 |
::deep a.nav-child-item {
|
| 75 |
+
padding: 0.54rem 0.7rem;
|
| 76 |
+
font-size: 0.84rem;
|
| 77 |
+
font-weight: 620;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
::deep a.nav-link-item.border-t {
|
| 81 |
+
border-top-color: rgba(148, 163, 184, 0.18) !important;
|
| 82 |
}
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/Login.razor
CHANGED
|
@@ -7,22 +7,25 @@
|
|
| 7 |
@inject MenuAuthorizationService MenuAuthorization
|
| 8 |
@inject IJSRuntime JS
|
| 9 |
|
| 10 |
-
<PageTitle>Sign In
|
| 11 |
|
| 12 |
-
<div class="flex min-h-screen items-center justify-center bg-slate-50
|
| 13 |
-
<div class="w-full max-w-md space-y-
|
| 14 |
<div>
|
| 15 |
<div class="flex justify-center">
|
| 16 |
-
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white
|
|
|
|
|
|
|
| 17 |
</div>
|
| 18 |
-
<h2 class="mt-
|
|
|
|
| 19 |
</div>
|
| 20 |
|
| 21 |
-
<form method="post" action="/account/login" class="
|
| 22 |
<AntiforgeryToken />
|
| 23 |
<input type="hidden" name="ReturnUrl" value="@ReturnUrl" />
|
| 24 |
|
| 25 |
-
<div class="space-y-4
|
| 26 |
<div>
|
| 27 |
<label for="usernameOrEmail" class="block text-sm font-medium text-slate-700 mb-1">Username or Email</label>
|
| 28 |
<input id="usernameOrEmail" name="UsernameOrEmail" required placeholder="Enter username or email"
|
|
@@ -30,7 +33,7 @@
|
|
| 30 |
</div>
|
| 31 |
<div class="relative">
|
| 32 |
<label for="password" class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
| 33 |
-
<input id="password" name="Password" type="@(showPassword ? "text" : "password")" required placeholder="
|
| 34 |
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all pr-10" />
|
| 35 |
<button type="button" @onclick="TogglePassword" class="absolute right-3 top-[28px] text-slate-400 hover:text-slate-600 focus:outline-none">
|
| 36 |
<i data-lucide="@(showPassword ? "eye-off" : "eye")" class="h-5 w-5"></i>
|
|
@@ -78,7 +81,7 @@
|
|
| 78 |
</p>
|
| 79 |
|
| 80 |
<div>
|
| 81 |
-
<button type="submit" class="group relative flex w-full justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-
|
| 82 |
Sign In
|
| 83 |
</button>
|
| 84 |
</div>
|
|
|
|
| 7 |
@inject MenuAuthorizationService MenuAuthorization
|
| 8 |
@inject IJSRuntime JS
|
| 9 |
|
| 10 |
+
<PageTitle>Sign In - Taskify</PageTitle>
|
| 11 |
|
| 12 |
+
<div class="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-10 sm:px-6 lg:px-8">
|
| 13 |
+
<div class="w-full max-w-md space-y-7 rounded-xl border border-slate-200 bg-white p-8 shadow-sm">
|
| 14 |
<div>
|
| 15 |
<div class="flex justify-center">
|
| 16 |
+
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white shadow-sm">
|
| 17 |
+
<i data-lucide="flame" class="h-6 w-6 fill-current"></i>
|
| 18 |
+
</span>
|
| 19 |
</div>
|
| 20 |
+
<h2 class="mt-5 text-center text-2xl font-bold text-slate-900">Sign in to Taskify</h2>
|
| 21 |
+
<p class="mt-2 text-center text-sm text-slate-500">Continue to your work dashboard.</p>
|
| 22 |
</div>
|
| 23 |
|
| 24 |
+
<form method="post" action="/account/login" class="space-y-5">
|
| 25 |
<AntiforgeryToken />
|
| 26 |
<input type="hidden" name="ReturnUrl" value="@ReturnUrl" />
|
| 27 |
|
| 28 |
+
<div class="space-y-4">
|
| 29 |
<div>
|
| 30 |
<label for="usernameOrEmail" class="block text-sm font-medium text-slate-700 mb-1">Username or Email</label>
|
| 31 |
<input id="usernameOrEmail" name="UsernameOrEmail" required placeholder="Enter username or email"
|
|
|
|
| 33 |
</div>
|
| 34 |
<div class="relative">
|
| 35 |
<label for="password" class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
| 36 |
+
<input id="password" name="Password" type="@(showPassword ? "text" : "password")" required placeholder="Password"
|
| 37 |
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all pr-10" />
|
| 38 |
<button type="button" @onclick="TogglePassword" class="absolute right-3 top-[28px] text-slate-400 hover:text-slate-600 focus:outline-none">
|
| 39 |
<i data-lucide="@(showPassword ? "eye-off" : "eye")" class="h-5 w-5"></i>
|
|
|
|
| 81 |
</p>
|
| 82 |
|
| 83 |
<div>
|
| 84 |
+
<button type="submit" class="group relative flex w-full justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-700">
|
| 85 |
Sign In
|
| 86 |
</button>
|
| 87 |
</div>
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/Register.razor
CHANGED
|
@@ -5,26 +5,27 @@
|
|
| 5 |
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
| 6 |
@inject IJSRuntime JS
|
| 7 |
|
| 8 |
-
<PageTitle>Register
|
| 9 |
|
| 10 |
-
<div class="flex min-h-screen items-center justify-center bg-slate-50
|
| 11 |
-
<div class="w-full max-w-md space-y-
|
| 12 |
<div>
|
| 13 |
<div class="flex justify-center">
|
| 14 |
-
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white
|
|
|
|
|
|
|
| 15 |
</div>
|
| 16 |
-
<h2 class="mt-
|
| 17 |
<p class="mt-2 text-center text-sm text-slate-600">
|
| 18 |
-
|
| 19 |
-
<a href="/login" class="font-
|
| 20 |
-
|
| 21 |
</p>
|
| 22 |
</div>
|
| 23 |
|
| 24 |
-
<form method="post" action="/account/register" class="
|
| 25 |
<AntiforgeryToken />
|
| 26 |
|
| 27 |
-
<div class="space-y-4
|
| 28 |
<div class="grid grid-cols-2 gap-4">
|
| 29 |
<div>
|
| 30 |
<label for="firstName" class="block text-sm font-medium text-slate-700 mb-1">First Name</label>
|
|
@@ -54,7 +55,7 @@
|
|
| 54 |
</div>
|
| 55 |
<div class="relative">
|
| 56 |
<label for="password" class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
| 57 |
-
<input id="password" name="Password" type="@(showPassword ? "text" : "password")" required placeholder="
|
| 58 |
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all pr-10" />
|
| 59 |
<button type="button" @onclick="TogglePassword" class="absolute right-3 top-[28px] text-slate-400 hover:text-slate-600 focus:outline-none">
|
| 60 |
<i data-lucide="@(showPassword ? "eye-off" : "eye")" class="h-5 w-5"></i>
|
|
@@ -70,7 +71,7 @@
|
|
| 70 |
}
|
| 71 |
|
| 72 |
<div>
|
| 73 |
-
<button type="submit" class="group relative flex w-full justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-
|
| 74 |
Register
|
| 75 |
</button>
|
| 76 |
</div>
|
|
|
|
| 5 |
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
| 6 |
@inject IJSRuntime JS
|
| 7 |
|
| 8 |
+
<PageTitle>Register - Taskify</PageTitle>
|
| 9 |
|
| 10 |
+
<div class="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-10 sm:px-6 lg:px-8">
|
| 11 |
+
<div class="w-full max-w-md space-y-7 rounded-xl border border-slate-200 bg-white p-8 shadow-sm">
|
| 12 |
<div>
|
| 13 |
<div class="flex justify-center">
|
| 14 |
+
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white shadow-sm">
|
| 15 |
+
<i data-lucide="flame" class="h-6 w-6 fill-current"></i>
|
| 16 |
+
</span>
|
| 17 |
</div>
|
| 18 |
+
<h2 class="mt-5 text-center text-2xl font-bold text-slate-900">Create your Taskify account</h2>
|
| 19 |
<p class="mt-2 text-center text-sm text-slate-600">
|
| 20 |
+
Already have access?
|
| 21 |
+
<a href="/login" class="font-semibold text-violet-600 hover:underline">Sign in</a>
|
|
|
|
| 22 |
</p>
|
| 23 |
</div>
|
| 24 |
|
| 25 |
+
<form method="post" action="/account/register" class="space-y-5">
|
| 26 |
<AntiforgeryToken />
|
| 27 |
|
| 28 |
+
<div class="space-y-4">
|
| 29 |
<div class="grid grid-cols-2 gap-4">
|
| 30 |
<div>
|
| 31 |
<label for="firstName" class="block text-sm font-medium text-slate-700 mb-1">First Name</label>
|
|
|
|
| 55 |
</div>
|
| 56 |
<div class="relative">
|
| 57 |
<label for="password" class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
| 58 |
+
<input id="password" name="Password" type="@(showPassword ? "text" : "password")" required placeholder="Password"
|
| 59 |
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all pr-10" />
|
| 60 |
<button type="button" @onclick="TogglePassword" class="absolute right-3 top-[28px] text-slate-400 hover:text-slate-600 focus:outline-none">
|
| 61 |
<i data-lucide="@(showPassword ? "eye-off" : "eye")" class="h-5 w-5"></i>
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
<div>
|
| 74 |
+
<button type="submit" class="group relative flex w-full justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-700">
|
| 75 |
Register
|
| 76 |
</button>
|
| 77 |
</div>
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Auth/ResetPassword.razor
CHANGED
|
@@ -3,25 +3,28 @@
|
|
| 3 |
@rendermode @(new InteractiveServerRenderMode(prerender: false))
|
| 4 |
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
| 5 |
@inject NavigationManager Navigation
|
|
|
|
| 6 |
|
| 7 |
-
<PageTitle>Reset Password -
|
| 8 |
|
| 9 |
-
<div class="flex min-h-screen items-center justify-center bg-slate-50
|
| 10 |
-
<div class="w-full max-w-md space-y-
|
| 11 |
<div>
|
| 12 |
<div class="flex justify-center">
|
| 13 |
-
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white
|
|
|
|
|
|
|
| 14 |
</div>
|
| 15 |
-
<h2 class="mt-
|
| 16 |
<p class="mt-2 text-center text-sm text-slate-600">
|
| 17 |
Use the recovery code configured in the API to set a new password.
|
| 18 |
</p>
|
| 19 |
</div>
|
| 20 |
|
| 21 |
-
<form method="post" action="/account/reset-password" class="
|
| 22 |
<AntiforgeryToken />
|
| 23 |
|
| 24 |
-
<div class="space-y-4
|
| 25 |
<div>
|
| 26 |
<label for="usernameOrEmail" class="block text-sm font-medium text-slate-700 mb-1">Username or Email</label>
|
| 27 |
<input id="usernameOrEmail" name="UsernameOrEmail" required placeholder="Enter username or email"
|
|
@@ -48,7 +51,7 @@
|
|
| 48 |
|
| 49 |
<div class="flex items-center justify-between">
|
| 50 |
<a href="/login" class="text-sm font-medium text-violet-600 hover:underline">Back to sign in</a>
|
| 51 |
-
<button type="submit" class="group relative flex justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-
|
| 52 |
Reset Password
|
| 53 |
</button>
|
| 54 |
</div>
|
|
@@ -62,4 +65,9 @@
|
|
| 62 |
|
| 63 |
private bool ShowError => !string.IsNullOrWhiteSpace(Error);
|
| 64 |
private string ErrorMessage => string.IsNullOrWhiteSpace(Error) ? "Unable to reset password." : Error;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
|
|
|
| 3 |
@rendermode @(new InteractiveServerRenderMode(prerender: false))
|
| 4 |
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
| 5 |
@inject NavigationManager Navigation
|
| 6 |
+
@inject IJSRuntime JS
|
| 7 |
|
| 8 |
+
<PageTitle>Reset Password - Taskify</PageTitle>
|
| 9 |
|
| 10 |
+
<div class="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-10 sm:px-6 lg:px-8">
|
| 11 |
+
<div class="w-full max-w-md space-y-7 rounded-xl border border-slate-200 bg-white p-8 shadow-sm">
|
| 12 |
<div>
|
| 13 |
<div class="flex justify-center">
|
| 14 |
+
<span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white shadow-sm">
|
| 15 |
+
<i data-lucide="flame" class="h-6 w-6 fill-current"></i>
|
| 16 |
+
</span>
|
| 17 |
</div>
|
| 18 |
+
<h2 class="mt-5 text-center text-2xl font-bold text-slate-900">Reset your password</h2>
|
| 19 |
<p class="mt-2 text-center text-sm text-slate-600">
|
| 20 |
Use the recovery code configured in the API to set a new password.
|
| 21 |
</p>
|
| 22 |
</div>
|
| 23 |
|
| 24 |
+
<form method="post" action="/account/reset-password" class="space-y-5">
|
| 25 |
<AntiforgeryToken />
|
| 26 |
|
| 27 |
+
<div class="space-y-4">
|
| 28 |
<div>
|
| 29 |
<label for="usernameOrEmail" class="block text-sm font-medium text-slate-700 mb-1">Username or Email</label>
|
| 30 |
<input id="usernameOrEmail" name="UsernameOrEmail" required placeholder="Enter username or email"
|
|
|
|
| 51 |
|
| 52 |
<div class="flex items-center justify-between">
|
| 53 |
<a href="/login" class="text-sm font-medium text-violet-600 hover:underline">Back to sign in</a>
|
| 54 |
+
<button type="submit" class="group relative flex justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-700">
|
| 55 |
Reset Password
|
| 56 |
</button>
|
| 57 |
</div>
|
|
|
|
| 65 |
|
| 66 |
private bool ShowError => !string.IsNullOrWhiteSpace(Error);
|
| 67 |
private string ErrorMessage => string.IsNullOrWhiteSpace(Error) ? "Unable to reset password." : Error;
|
| 68 |
+
|
| 69 |
+
protected override async Task OnAfterRenderAsync(bool firstRender)
|
| 70 |
+
{
|
| 71 |
+
await JS.InvokeVoidAsync("initIcons");
|
| 72 |
+
}
|
| 73 |
}
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/EmployeeDashboard.razor
CHANGED
|
@@ -12,18 +12,18 @@
|
|
| 12 |
|
| 13 |
<div class="min-h-screen bg-slate-50 px-4 py-6 text-slate-900 sm:px-6 lg:px-8">
|
| 14 |
<div class="mx-auto max-w-[1440px] space-y-6">
|
| 15 |
-
<div class="rounded-
|
| 16 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 17 |
<div>
|
| 18 |
-
<p class="text-sm font-semibold uppercase tracking-[0.
|
| 19 |
-
<h2 class="mt-2 text-
|
| 20 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 21 |
Tasks, deadlines, and your progress in one place.
|
| 22 |
</p>
|
| 23 |
</div>
|
| 24 |
|
| 25 |
<div class="flex flex-wrap gap-3">
|
| 26 |
-
<a href="/tasks/all" class="inline-flex items-center rounded-2xl bg-violet-700 px-5 py-3 text-sm font-semibold text-white shadow-
|
| 27 |
<i data-lucide="check-square" class="mr-2 h-4 w-4"></i>
|
| 28 |
View Tasks
|
| 29 |
</a>
|
|
@@ -37,7 +37,7 @@
|
|
| 37 |
|
| 38 |
@if (isLoading)
|
| 39 |
{
|
| 40 |
-
<div class="rounded-
|
| 41 |
<div class="mx-auto flex items-center justify-center gap-3 text-slate-500">
|
| 42 |
<svg class="h-5 w-5 animate-spin" viewBox="0 0 24 24">
|
| 43 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
@@ -49,7 +49,7 @@
|
|
| 49 |
}
|
| 50 |
else if (!string.IsNullOrWhiteSpace(errorMessage))
|
| 51 |
{
|
| 52 |
-
<div class="rounded-
|
| 53 |
@errorMessage
|
| 54 |
</div>
|
| 55 |
}
|
|
@@ -58,7 +58,7 @@
|
|
| 58 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
| 59 |
@foreach (var metric in SummaryMetrics)
|
| 60 |
{
|
| 61 |
-
<div class="rounded-
|
| 62 |
<div class="flex items-center justify-between">
|
| 63 |
<div class="@metric.IconWrapClass">
|
| 64 |
<i data-lucide="@metric.Icon" class="h-5 w-5"></i>
|
|
@@ -73,14 +73,14 @@
|
|
| 73 |
|
| 74 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 75 |
<div class="space-y-5">
|
| 76 |
-
<div class="overflow-hidden rounded-
|
| 77 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 78 |
<div class="flex items-center gap-3">
|
| 79 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-violet-200 bg-violet-50 text-violet-700">
|
| 80 |
<i data-lucide="timer-reset" class="h-4 w-4"></i>
|
| 81 |
</span>
|
| 82 |
<div>
|
| 83 |
-
<h3 class="text-
|
| 84 |
<p class="text-sm text-slate-500">Tasks currently being worked on</p>
|
| 85 |
</div>
|
| 86 |
</div>
|
|
@@ -112,14 +112,14 @@
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
-
<div class="overflow-hidden rounded-
|
| 116 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 117 |
<div class="flex items-center gap-3">
|
| 118 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-sky-200 bg-sky-50 text-sky-600">
|
| 119 |
<i data-lucide="clock-3" class="h-4 w-4"></i>
|
| 120 |
</span>
|
| 121 |
<div>
|
| 122 |
-
<h3 class="text-
|
| 123 |
<p class="text-sm text-slate-500">Tasks waiting for action or coming up soon</p>
|
| 124 |
</div>
|
| 125 |
</div>
|
|
@@ -151,14 +151,14 @@
|
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
-
<div class="overflow-hidden rounded-
|
| 155 |
<div class="flex items-center justify-between gap-4 border-b border-rose-100 px-6 py-5">
|
| 156 |
<div class="flex items-center gap-3">
|
| 157 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-rose-200 bg-rose-50 text-rose-600">
|
| 158 |
<i data-lucide="triangle-alert" class="h-4 w-4"></i>
|
| 159 |
</span>
|
| 160 |
<div>
|
| 161 |
-
<h3 class="text-
|
| 162 |
<p class="text-sm text-slate-500">Tasks past their due date</p>
|
| 163 |
</div>
|
| 164 |
</div>
|
|
@@ -194,14 +194,14 @@
|
|
| 194 |
</div>
|
| 195 |
|
| 196 |
<div class="space-y-5">
|
| 197 |
-
<div class="rounded-
|
| 198 |
<div class="flex items-start justify-between gap-4">
|
| 199 |
<div>
|
| 200 |
<div class="flex items-center gap-3">
|
| 201 |
<button type="button" @onclick="PrevMonth" class="inline-flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-50">
|
| 202 |
<i data-lucide="chevron-left" class="h-4 w-4"></i>
|
| 203 |
</button>
|
| 204 |
-
<h3 class="text-
|
| 205 |
<button type="button" @onclick="NextMonth" class="inline-flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-50">
|
| 206 |
<i data-lucide="chevron-right" class="h-4 w-4"></i>
|
| 207 |
</button>
|
|
@@ -239,9 +239,9 @@
|
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
|
| 242 |
-
<div class="overflow-hidden rounded-
|
| 243 |
<div class="border-b border-slate-100 px-6 py-5">
|
| 244 |
-
<h3 class="text-
|
| 245 |
<p class="mt-1 text-sm text-slate-500">Tasks marked on @SelectedDateLabel</p>
|
| 246 |
</div>
|
| 247 |
|
|
@@ -446,7 +446,7 @@
|
|
| 446 |
private string GetStatusColor(AppTaskStatus statusId) => statusId switch
|
| 447 |
{
|
| 448 |
AppTaskStatus.Todo => "#22d3ee",
|
| 449 |
-
AppTaskStatus.InProgress => "#
|
| 450 |
AppTaskStatus.Done => "#10b981",
|
| 451 |
_ => "#94a3b8"
|
| 452 |
};
|
|
|
|
| 12 |
|
| 13 |
<div class="min-h-screen bg-slate-50 px-4 py-6 text-slate-900 sm:px-6 lg:px-8">
|
| 14 |
<div class="mx-auto max-w-[1440px] space-y-6">
|
| 15 |
+
<div class="rounded-xl border border-slate-200 bg-white px-8 py-8 shadow-sm">
|
| 16 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 17 |
<div>
|
| 18 |
+
<p class="text-sm font-semibold uppercase tracking-[0.08em] text-violet-700">Team Member</p>
|
| 19 |
+
<h2 class="mt-2 text-3xl font-bold tracking-tight text-slate-950">My Work</h2>
|
| 20 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 21 |
Tasks, deadlines, and your progress in one place.
|
| 22 |
</p>
|
| 23 |
</div>
|
| 24 |
|
| 25 |
<div class="flex flex-wrap gap-3">
|
| 26 |
+
<a href="/tasks/all" class="inline-flex items-center rounded-2xl bg-violet-700 px-5 py-3 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-600">
|
| 27 |
<i data-lucide="check-square" class="mr-2 h-4 w-4"></i>
|
| 28 |
View Tasks
|
| 29 |
</a>
|
|
|
|
| 37 |
|
| 38 |
@if (isLoading)
|
| 39 |
{
|
| 40 |
+
<div class="rounded-xl border border-slate-200 bg-white px-6 py-12 text-center shadow-sm">
|
| 41 |
<div class="mx-auto flex items-center justify-center gap-3 text-slate-500">
|
| 42 |
<svg class="h-5 w-5 animate-spin" viewBox="0 0 24 24">
|
| 43 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
|
|
| 49 |
}
|
| 50 |
else if (!string.IsNullOrWhiteSpace(errorMessage))
|
| 51 |
{
|
| 52 |
+
<div class="rounded-xl border border-rose-200 bg-rose-50 px-6 py-5 text-sm text-rose-700 shadow-sm">
|
| 53 |
@errorMessage
|
| 54 |
</div>
|
| 55 |
}
|
|
|
|
| 58 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
| 59 |
@foreach (var metric in SummaryMetrics)
|
| 60 |
{
|
| 61 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-transform duration-200 hover:-translate-y-1">
|
| 62 |
<div class="flex items-center justify-between">
|
| 63 |
<div class="@metric.IconWrapClass">
|
| 64 |
<i data-lucide="@metric.Icon" class="h-5 w-5"></i>
|
|
|
|
| 73 |
|
| 74 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 75 |
<div class="space-y-5">
|
| 76 |
+
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
| 77 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 78 |
<div class="flex items-center gap-3">
|
| 79 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-violet-200 bg-violet-50 text-violet-700">
|
| 80 |
<i data-lucide="timer-reset" class="h-4 w-4"></i>
|
| 81 |
</span>
|
| 82 |
<div>
|
| 83 |
+
<h3 class="text-xl font-bold text-slate-950">In Progress</h3>
|
| 84 |
<p class="text-sm text-slate-500">Tasks currently being worked on</p>
|
| 85 |
</div>
|
| 86 |
</div>
|
|
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
+
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
| 116 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 117 |
<div class="flex items-center gap-3">
|
| 118 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-sky-200 bg-sky-50 text-sky-600">
|
| 119 |
<i data-lucide="clock-3" class="h-4 w-4"></i>
|
| 120 |
</span>
|
| 121 |
<div>
|
| 122 |
+
<h3 class="text-xl font-bold text-slate-950">To Do & Upcoming</h3>
|
| 123 |
<p class="text-sm text-slate-500">Tasks waiting for action or coming up soon</p>
|
| 124 |
</div>
|
| 125 |
</div>
|
|
|
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
+
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
| 155 |
<div class="flex items-center justify-between gap-4 border-b border-rose-100 px-6 py-5">
|
| 156 |
<div class="flex items-center gap-3">
|
| 157 |
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-rose-200 bg-rose-50 text-rose-600">
|
| 158 |
<i data-lucide="triangle-alert" class="h-4 w-4"></i>
|
| 159 |
</span>
|
| 160 |
<div>
|
| 161 |
+
<h3 class="text-xl font-bold text-slate-950">Overdue</h3>
|
| 162 |
<p class="text-sm text-slate-500">Tasks past their due date</p>
|
| 163 |
</div>
|
| 164 |
</div>
|
|
|
|
| 194 |
</div>
|
| 195 |
|
| 196 |
<div class="space-y-5">
|
| 197 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
| 198 |
<div class="flex items-start justify-between gap-4">
|
| 199 |
<div>
|
| 200 |
<div class="flex items-center gap-3">
|
| 201 |
<button type="button" @onclick="PrevMonth" class="inline-flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-50">
|
| 202 |
<i data-lucide="chevron-left" class="h-4 w-4"></i>
|
| 203 |
</button>
|
| 204 |
+
<h3 class="text-xl font-bold text-slate-950 min-w-[120px]">@CalendarMonthLabel</h3>
|
| 205 |
<button type="button" @onclick="NextMonth" class="inline-flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-50">
|
| 206 |
<i data-lucide="chevron-right" class="h-4 w-4"></i>
|
| 207 |
</button>
|
|
|
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
|
| 242 |
+
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
| 243 |
<div class="border-b border-slate-100 px-6 py-5">
|
| 244 |
+
<h3 class="text-xl font-bold text-slate-950">Related Tasks</h3>
|
| 245 |
<p class="mt-1 text-sm text-slate-500">Tasks marked on @SelectedDateLabel</p>
|
| 246 |
</div>
|
| 247 |
|
|
|
|
| 446 |
private string GetStatusColor(AppTaskStatus statusId) => statusId switch
|
| 447 |
{
|
| 448 |
AppTaskStatus.Todo => "#22d3ee",
|
| 449 |
+
AppTaskStatus.InProgress => "#3b82f6",
|
| 450 |
AppTaskStatus.Done => "#10b981",
|
| 451 |
_ => "#94a3b8"
|
| 452 |
};
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/Home.razor
CHANGED
|
@@ -19,11 +19,11 @@
|
|
| 19 |
<PageTitle>Dashboard</PageTitle>
|
| 20 |
|
| 21 |
<div class="space-y-8 text-slate-900">
|
| 22 |
-
<div class="rounded-
|
| 23 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 24 |
<div>
|
| 25 |
-
<p class="text-sm font-semibold uppercase tracking-[0.
|
| 26 |
-
<h2 class="mt-2 text-
|
| 27 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 28 |
Projects, users, and team progress at a glance.
|
| 29 |
</p>
|
|
@@ -31,7 +31,7 @@
|
|
| 31 |
<div class="flex flex-wrap gap-3">
|
| 32 |
@if (canCreateTask)
|
| 33 |
{
|
| 34 |
-
<a href="/tasks/all" class="inline-flex items-center rounded-2xl bg-violet-600 px-5 py-3 text-sm font-semibold text-white shadow-
|
| 35 |
<i data-lucide="plus" class="mr-2 h-4 w-4"></i>
|
| 36 |
New Task
|
| 37 |
</a>
|
|
@@ -45,7 +45,7 @@
|
|
| 45 |
</div>
|
| 46 |
|
| 47 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
| 48 |
-
<div class="rounded-
|
| 49 |
<div class="flex items-center justify-between">
|
| 50 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-violet-500 text-white">
|
| 51 |
<i data-lucide="users" class="h-5 w-5"></i>
|
|
@@ -56,7 +56,7 @@
|
|
| 56 |
<p class="mt-2 text-sm text-slate-500">Total users</p>
|
| 57 |
</div>
|
| 58 |
|
| 59 |
-
<div class="rounded-
|
| 60 |
<div class="flex items-center justify-between">
|
| 61 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-sky-500 text-white">
|
| 62 |
<i data-lucide="folder-kanban" class="h-5 w-5"></i>
|
|
@@ -67,7 +67,7 @@
|
|
| 67 |
<p class="mt-2 text-sm text-slate-500">Total projects</p>
|
| 68 |
</div>
|
| 69 |
|
| 70 |
-
<div class="rounded-
|
| 71 |
<div class="flex items-center justify-between">
|
| 72 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500 text-white">
|
| 73 |
<i data-lucide="check-square" class="h-5 w-5"></i>
|
|
@@ -78,7 +78,7 @@
|
|
| 78 |
<p class="mt-2 text-sm text-slate-500">Active tasks</p>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
-
<div class="rounded-
|
| 82 |
<div class="flex items-center justify-between">
|
| 83 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-500 text-white">
|
| 84 |
<i data-lucide="trending-up" class="h-5 w-5"></i>
|
|
@@ -91,10 +91,10 @@
|
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 94 |
-
<div class="rounded-
|
| 95 |
<div class="flex items-start justify-between gap-4">
|
| 96 |
<div>
|
| 97 |
-
<h3 class="text-
|
| 98 |
<p class="mt-1 text-sm text-slate-500">Active vs completed tasks per month.</p>
|
| 99 |
</div>
|
| 100 |
<div class="text-sm text-slate-500">@ActivityRangeLabel</div>
|
|
@@ -105,8 +105,8 @@
|
|
| 105 |
<svg viewBox="0 0 760 340" class="h-[340px] w-full">
|
| 106 |
<defs>
|
| 107 |
<linearGradient id="activityFill" x1="0" x2="0" y1="0" y2="1">
|
| 108 |
-
<stop offset="0%" stop-color="#
|
| 109 |
-
<stop offset="100%" stop-color="#
|
| 110 |
</linearGradient>
|
| 111 |
</defs>
|
| 112 |
|
|
@@ -123,14 +123,14 @@
|
|
| 123 |
}
|
| 124 |
|
| 125 |
<path d="@ActivityAreaPath" fill="url(#activityFill)" />
|
| 126 |
-
<path d="@ActivityLinePath" fill="none" stroke="#
|
| 127 |
|
| 128 |
@for (var i = 0; i < ActivityMonths.Count; i++)
|
| 129 |
{
|
| 130 |
var index = i;
|
| 131 |
var point = ActivityPoints[i];
|
| 132 |
<circle cx="@point.X" cy="@point.Y" r="5"
|
| 133 |
-
fill="#
|
| 134 |
stroke="#ddd6fe"
|
| 135 |
stroke-width="3"
|
| 136 |
@onmouseenter="() => SetHoveredActivity(index)"
|
|
@@ -158,8 +158,8 @@
|
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
|
| 161 |
-
<div class="rounded-
|
| 162 |
-
<h3 class="text-
|
| 163 |
<p class="mt-1 text-sm text-slate-500">@TotalTasksCount total tasks</p>
|
| 164 |
|
| 165 |
<div class="mt-4 flex justify-center">
|
|
@@ -183,10 +183,10 @@
|
|
| 183 |
</div>
|
| 184 |
|
| 185 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.25fr)_minmax(340px,0.75fr)]">
|
| 186 |
-
<div class="rounded-
|
| 187 |
<div class="flex items-center justify-between gap-4">
|
| 188 |
<div>
|
| 189 |
-
<h3 class="text-
|
| 190 |
<p class="mt-1 text-sm text-slate-500">A quick snapshot of the current project list.</p>
|
| 191 |
</div>
|
| 192 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-violet-50">
|
|
@@ -221,10 +221,10 @@
|
|
| 221 |
</div>
|
| 222 |
</div>
|
| 223 |
|
| 224 |
-
<div class="rounded-
|
| 225 |
<div class="flex items-start justify-between gap-4">
|
| 226 |
<div>
|
| 227 |
-
<h3 class="text-
|
| 228 |
<p class="mt-1 text-sm text-slate-500">Today and upcoming project starts.</p>
|
| 229 |
</div>
|
| 230 |
<div class="text-right">
|
|
@@ -272,10 +272,10 @@
|
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
|
| 275 |
-
<div class="rounded-
|
| 276 |
<div class="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 277 |
<div>
|
| 278 |
-
<h3 class="text-
|
| 279 |
<p class="mt-1 text-sm text-slate-500">Browse every project in a gantt view and adjust the month window below.</p>
|
| 280 |
</div>
|
| 281 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-violet-50">
|
|
@@ -283,7 +283,7 @@
|
|
| 283 |
</a>
|
| 284 |
</div>
|
| 285 |
|
| 286 |
-
<div class="mt-4 flex flex-wrap items-end gap-3 rounded-
|
| 287 |
<div class="flex flex-col gap-1">
|
| 288 |
<label class="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">From</label>
|
| 289 |
<input type="month"
|
|
@@ -449,7 +449,7 @@
|
|
| 449 |
var pieData = new[]
|
| 450 |
{
|
| 451 |
new { name = "Completed", y = completedCount, color = "#10b981" },
|
| 452 |
-
new { name = "In Progress", y = inProgressCount, color = "#
|
| 453 |
new { name = "To Do", y = todoCount, color = "#06b6d4" },
|
| 454 |
new { name = "Overdue", y = overdueCount, color = "#ef4444" }
|
| 455 |
};
|
|
@@ -624,7 +624,7 @@
|
|
| 624 |
overdueCount
|
| 625 |
};
|
| 626 |
|
| 627 |
-
var colors = new[] { "#10b981", "#
|
| 628 |
|
| 629 |
// 2. Adjust total so it maps 100% cleanly to the math displayed
|
| 630 |
var total = Math.Max(TotalTasksCount, 1);
|
|
@@ -907,7 +907,7 @@
|
|
| 907 |
{
|
| 908 |
var palette = new[]
|
| 909 |
{
|
| 910 |
-
"#
|
| 911 |
"#0ea5e9",
|
| 912 |
"#10b981",
|
| 913 |
"#f59e0b",
|
|
|
|
| 19 |
<PageTitle>Dashboard</PageTitle>
|
| 20 |
|
| 21 |
<div class="space-y-8 text-slate-900">
|
| 22 |
+
<div class="rounded-xl border border-slate-200 bg-white px-8 py-8 text-slate-900 shadow-sm shadow-slate-200/60">
|
| 23 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 24 |
<div>
|
| 25 |
+
<p class="text-sm font-semibold uppercase tracking-[0.08em] text-violet-600">Overview</p>
|
| 26 |
+
<h2 class="mt-2 text-3xl font-bold tracking-tight text-slate-900">Welcome back, @currentUserFullName</h2>
|
| 27 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 28 |
Projects, users, and team progress at a glance.
|
| 29 |
</p>
|
|
|
|
| 31 |
<div class="flex flex-wrap gap-3">
|
| 32 |
@if (canCreateTask)
|
| 33 |
{
|
| 34 |
+
<a href="/tasks/all" class="inline-flex items-center rounded-2xl bg-violet-600 px-5 py-3 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-500">
|
| 35 |
<i data-lucide="plus" class="mr-2 h-4 w-4"></i>
|
| 36 |
New Task
|
| 37 |
</a>
|
|
|
|
| 45 |
</div>
|
| 46 |
|
| 47 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
| 48 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm shadow-slate-200/50">
|
| 49 |
<div class="flex items-center justify-between">
|
| 50 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-violet-500 text-white">
|
| 51 |
<i data-lucide="users" class="h-5 w-5"></i>
|
|
|
|
| 56 |
<p class="mt-2 text-sm text-slate-500">Total users</p>
|
| 57 |
</div>
|
| 58 |
|
| 59 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm shadow-slate-200/50">
|
| 60 |
<div class="flex items-center justify-between">
|
| 61 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-sky-500 text-white">
|
| 62 |
<i data-lucide="folder-kanban" class="h-5 w-5"></i>
|
|
|
|
| 67 |
<p class="mt-2 text-sm text-slate-500">Total projects</p>
|
| 68 |
</div>
|
| 69 |
|
| 70 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm shadow-slate-200/50">
|
| 71 |
<div class="flex items-center justify-between">
|
| 72 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500 text-white">
|
| 73 |
<i data-lucide="check-square" class="h-5 w-5"></i>
|
|
|
|
| 78 |
<p class="mt-2 text-sm text-slate-500">Active tasks</p>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm shadow-slate-200/50">
|
| 82 |
<div class="flex items-center justify-between">
|
| 83 |
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-500 text-white">
|
| 84 |
<i data-lucide="trending-up" class="h-5 w-5"></i>
|
|
|
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 94 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 text-slate-900 shadow-sm shadow-slate-200/60">
|
| 95 |
<div class="flex items-start justify-between gap-4">
|
| 96 |
<div>
|
| 97 |
+
<h3 class="text-xl font-bold text-slate-900">Project Activity</h3>
|
| 98 |
<p class="mt-1 text-sm text-slate-500">Active vs completed tasks per month.</p>
|
| 99 |
</div>
|
| 100 |
<div class="text-sm text-slate-500">@ActivityRangeLabel</div>
|
|
|
|
| 105 |
<svg viewBox="0 0 760 340" class="h-[340px] w-full">
|
| 106 |
<defs>
|
| 107 |
<linearGradient id="activityFill" x1="0" x2="0" y1="0" y2="1">
|
| 108 |
+
<stop offset="0%" stop-color="#2563eb" stop-opacity="0.18" />
|
| 109 |
+
<stop offset="100%" stop-color="#2563eb" stop-opacity="0" />
|
| 110 |
</linearGradient>
|
| 111 |
</defs>
|
| 112 |
|
|
|
|
| 123 |
}
|
| 124 |
|
| 125 |
<path d="@ActivityAreaPath" fill="url(#activityFill)" />
|
| 126 |
+
<path d="@ActivityLinePath" fill="none" stroke="#2563eb" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
| 127 |
|
| 128 |
@for (var i = 0; i < ActivityMonths.Count; i++)
|
| 129 |
{
|
| 130 |
var index = i;
|
| 131 |
var point = ActivityPoints[i];
|
| 132 |
<circle cx="@point.X" cy="@point.Y" r="5"
|
| 133 |
+
fill="#2563eb"
|
| 134 |
stroke="#ddd6fe"
|
| 135 |
stroke-width="3"
|
| 136 |
@onmouseenter="() => SetHoveredActivity(index)"
|
|
|
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
|
| 161 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 text-slate-900 shadow-sm shadow-slate-200/60">
|
| 162 |
+
<h3 class="text-xl font-bold text-slate-900">Task Distribution</h3>
|
| 163 |
<p class="mt-1 text-sm text-slate-500">@TotalTasksCount total tasks</p>
|
| 164 |
|
| 165 |
<div class="mt-4 flex justify-center">
|
|
|
|
| 183 |
</div>
|
| 184 |
|
| 185 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.25fr)_minmax(340px,0.75fr)]">
|
| 186 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/60">
|
| 187 |
<div class="flex items-center justify-between gap-4">
|
| 188 |
<div>
|
| 189 |
+
<h3 class="text-xl font-bold text-slate-900">All Projects</h3>
|
| 190 |
<p class="mt-1 text-sm text-slate-500">A quick snapshot of the current project list.</p>
|
| 191 |
</div>
|
| 192 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-violet-50">
|
|
|
|
| 221 |
</div>
|
| 222 |
</div>
|
| 223 |
|
| 224 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 text-slate-900 shadow-sm shadow-slate-200/60">
|
| 225 |
<div class="flex items-start justify-between gap-4">
|
| 226 |
<div>
|
| 227 |
+
<h3 class="text-xl font-bold text-slate-900">Calendar</h3>
|
| 228 |
<p class="mt-1 text-sm text-slate-500">Today and upcoming project starts.</p>
|
| 229 |
</div>
|
| 230 |
<div class="text-right">
|
|
|
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
|
| 275 |
+
<div class="rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm shadow-slate-200/60">
|
| 276 |
<div class="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 277 |
<div>
|
| 278 |
+
<h3 class="text-xl font-bold text-slate-900">Project Timeline</h3>
|
| 279 |
<p class="mt-1 text-sm text-slate-500">Browse every project in a gantt view and adjust the month window below.</p>
|
| 280 |
</div>
|
| 281 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-violet-50">
|
|
|
|
| 283 |
</a>
|
| 284 |
</div>
|
| 285 |
|
| 286 |
+
<div class="mt-4 flex flex-wrap items-end gap-3 rounded-xl border border-slate-200 bg-slate-50 px-3 py-3">
|
| 287 |
<div class="flex flex-col gap-1">
|
| 288 |
<label class="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">From</label>
|
| 289 |
<input type="month"
|
|
|
|
| 449 |
var pieData = new[]
|
| 450 |
{
|
| 451 |
new { name = "Completed", y = completedCount, color = "#10b981" },
|
| 452 |
+
new { name = "In Progress", y = inProgressCount, color = "#3b82f6" },
|
| 453 |
new { name = "To Do", y = todoCount, color = "#06b6d4" },
|
| 454 |
new { name = "Overdue", y = overdueCount, color = "#ef4444" }
|
| 455 |
};
|
|
|
|
| 624 |
overdueCount
|
| 625 |
};
|
| 626 |
|
| 627 |
+
var colors = new[] { "#10b981", "#3b82f6", "#06b6d4", "#ef4444" };
|
| 628 |
|
| 629 |
// 2. Adjust total so it maps 100% cleanly to the math displayed
|
| 630 |
var total = Math.Max(TotalTasksCount, 1);
|
|
|
|
| 907 |
{
|
| 908 |
var palette = new[]
|
| 909 |
{
|
| 910 |
+
"#2563eb",
|
| 911 |
"#0ea5e9",
|
| 912 |
"#10b981",
|
| 913 |
"#f59e0b",
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Dashboard/ManagerDashboard.razor
CHANGED
|
@@ -11,11 +11,11 @@
|
|
| 11 |
|
| 12 |
<div class="min-h-screen bg-slate-50 px-4 py-6 text-slate-900 sm:px-6 lg:px-8">
|
| 13 |
<div class="mx-auto max-w-[1440px] space-y-6">
|
| 14 |
-
<div class="rounded-
|
| 15 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 16 |
<div>
|
| 17 |
-
<p class="text-sm font-semibold uppercase tracking-[0.
|
| 18 |
-
<h2 class="mt-2 text-
|
| 19 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 20 |
Track your team, project health, and weekly task progress in one place.
|
| 21 |
@if (summary.PendingTasksCount > 0)
|
|
@@ -26,7 +26,7 @@
|
|
| 26 |
</div>
|
| 27 |
|
| 28 |
<div class="flex flex-wrap gap-3">
|
| 29 |
-
<a href="/projects/all" class="inline-flex items-center rounded-2xl bg-violet-700 px-5 py-3 text-sm font-semibold text-white shadow-
|
| 30 |
<i data-lucide="folder-kanban" class="mr-2 h-4 w-4"></i>
|
| 31 |
All Projects
|
| 32 |
</a>
|
|
@@ -40,7 +40,7 @@
|
|
| 40 |
|
| 41 |
@if (isLoading)
|
| 42 |
{
|
| 43 |
-
<div class="rounded-
|
| 44 |
<div class="mx-auto flex items-center justify-center gap-3 text-slate-500">
|
| 45 |
<svg class="h-5 w-5 animate-spin" viewBox="0 0 24 24">
|
| 46 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
@@ -52,7 +52,7 @@
|
|
| 52 |
}
|
| 53 |
else if (!string.IsNullOrWhiteSpace(errorMessage))
|
| 54 |
{
|
| 55 |
-
<div class="rounded-
|
| 56 |
@errorMessage
|
| 57 |
</div>
|
| 58 |
}
|
|
@@ -61,7 +61,7 @@
|
|
| 61 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
| 62 |
@foreach (var metric in SummaryMetrics)
|
| 63 |
{
|
| 64 |
-
<div class="group relative overflow-hidden rounded-
|
| 65 |
<div class="relative flex items-center justify-between">
|
| 66 |
<div class="@metric.IconWrapClass">
|
| 67 |
<i data-lucide="@metric.Icon" class="h-5 w-5"></i>
|
|
@@ -75,10 +75,10 @@
|
|
| 75 |
</div>
|
| 76 |
|
| 77 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 78 |
-
<div class="rounded-
|
| 79 |
<div class="flex items-start justify-between gap-4">
|
| 80 |
<div>
|
| 81 |
-
<h3 class="text-
|
| 82 |
<p class="mt-1 text-sm text-slate-500">Completed vs pending tasks per week</p>
|
| 83 |
</div>
|
| 84 |
<div class="text-sm text-slate-500">@WeeklyRangeLabel</div>
|
|
@@ -103,7 +103,7 @@
|
|
| 103 |
<g @onmouseenter="() => SetHoveredWeek(index)" @onmouseleave="ClearHoveredWeek">
|
| 104 |
<rect x="@week.HoverX" y="40" width="@week.HoverWidth" height="160" rx="18" fill="@(hoveredWeekIndex == index ? "#f8fafc" : "transparent")" opacity="@(hoveredWeekIndex == index ? "1" : "0")" />
|
| 105 |
|
| 106 |
-
<rect x="@week.CompletedX" y="@(week.BaseY - week.CompletedHeight)" width="@BarWidth" height="@week.CompletedHeight" rx="8" fill="#
|
| 107 |
<rect x="@week.PendingX" y="@(week.BaseY - week.PendingHeight)" width="@BarWidth" height="@week.PendingHeight" rx="8" fill="#14b8a6" />
|
| 108 |
|
| 109 |
<text x="@week.LabelX" y="246" text-anchor="middle" font-size="13" fill="#64748b">@week.Label</text>
|
|
@@ -113,7 +113,7 @@
|
|
| 113 |
|
| 114 |
@if (hoveredWeek is not null)
|
| 115 |
{
|
| 116 |
-
<div class="pointer-events-none absolute z-20 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-
|
| 117 |
style="@HoveredWeekTooltipStyle">
|
| 118 |
<div class="text-sm font-semibold text-slate-950">@hoveredWeek.Label</div>
|
| 119 |
<div class="mt-2 text-sm text-violet-700">Completed: @hoveredWeek.Completed</div>
|
|
@@ -139,8 +139,8 @@
|
|
| 139 |
</div>
|
| 140 |
</div>
|
| 141 |
|
| 142 |
-
<div class="rounded-
|
| 143 |
-
<h3 class="text-
|
| 144 |
<p class="mt-1 text-sm text-slate-500">Members with active workload</p>
|
| 145 |
|
| 146 |
<div class="mt-6 max-h-[460px] space-y-4 overflow-y-auto pr-1">
|
|
@@ -181,10 +181,10 @@
|
|
| 181 |
</div>
|
| 182 |
|
| 183 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.05fr)_minmax(0,0.95fr)]">
|
| 184 |
-
<div class="rounded-
|
| 185 |
<div class="flex items-start justify-between gap-4">
|
| 186 |
<div>
|
| 187 |
-
<h3 class="text-
|
| 188 |
<p class="mt-1 text-sm text-slate-500">@CalendarTodayLabel</p>
|
| 189 |
</div>
|
| 190 |
</div>
|
|
@@ -214,8 +214,8 @@
|
|
| 214 |
</div>
|
| 215 |
</div>
|
| 216 |
|
| 217 |
-
<div class="rounded-
|
| 218 |
-
<h3 class="text-
|
| 219 |
<p class="mt-1 text-sm text-slate-500">Projects ending soon</p>
|
| 220 |
|
| 221 |
<div class="mt-6 max-h-[460px] space-y-3 overflow-y-auto pr-1">
|
|
@@ -243,10 +243,10 @@
|
|
| 243 |
</div>
|
| 244 |
</div>
|
| 245 |
|
| 246 |
-
<div class="overflow-hidden rounded-
|
| 247 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 248 |
<div>
|
| 249 |
-
<h3 class="text-
|
| 250 |
<p class="mt-1 text-sm text-slate-500">Click "View Tasks" to drill in</p>
|
| 251 |
</div>
|
| 252 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-slate-50">
|
|
@@ -281,8 +281,8 @@
|
|
| 281 |
|
| 282 |
<div class="flex flex-col items-start gap-3 lg:items-end">
|
| 283 |
<div class="text-right">
|
| 284 |
-
<div class="text-
|
| 285 |
-
<div class="text-xs uppercase tracking-[0.
|
| 286 |
</div>
|
| 287 |
<button @onclick="() => ViewProjectTasks(project.Id)" class="inline-flex items-center rounded-2xl bg-violet-700 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-600">
|
| 288 |
<i data-lucide="eye" class="mr-2 h-4 w-4"></i>
|
|
@@ -449,7 +449,7 @@
|
|
| 449 |
private List<ProjectCardViewModel> BuildProjectCards()
|
| 450 |
{
|
| 451 |
var progressByProject = projectProgress.ToDictionary(item => item.ProjectId, item => item);
|
| 452 |
-
var palette = new[] { "#
|
| 453 |
|
| 454 |
return projects
|
| 455 |
.OrderByDescending(project => progressByProject.TryGetValue(project.Id, out var progress) ? progress.CompletionPercentage : 0)
|
|
@@ -494,7 +494,7 @@
|
|
| 494 |
|
| 495 |
private List<TeamProgressViewModel> BuildTeamProgress()
|
| 496 |
{
|
| 497 |
-
var palette = new[] { "#
|
| 498 |
|
| 499 |
return users
|
| 500 |
.Select((user, index) =>
|
|
@@ -695,7 +695,7 @@
|
|
| 695 |
{
|
| 696 |
var palette = new[]
|
| 697 |
{
|
| 698 |
-
"#
|
| 699 |
"#0ea5e9",
|
| 700 |
"#10b981",
|
| 701 |
"#f59e0b",
|
|
|
|
| 11 |
|
| 12 |
<div class="min-h-screen bg-slate-50 px-4 py-6 text-slate-900 sm:px-6 lg:px-8">
|
| 13 |
<div class="mx-auto max-w-[1440px] space-y-6">
|
| 14 |
+
<div class="rounded-xl border border-slate-200 bg-white px-8 py-8 shadow-sm">
|
| 15 |
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
| 16 |
<div>
|
| 17 |
+
<p class="text-sm font-semibold uppercase tracking-[0.08em] text-violet-700">Team View</p>
|
| 18 |
+
<h2 class="mt-2 text-3xl font-bold tracking-tight text-slate-950">My Projects</h2>
|
| 19 |
<p class="mt-3 max-w-2xl text-sm text-slate-600">
|
| 20 |
Track your team, project health, and weekly task progress in one place.
|
| 21 |
@if (summary.PendingTasksCount > 0)
|
|
|
|
| 26 |
</div>
|
| 27 |
|
| 28 |
<div class="flex flex-wrap gap-3">
|
| 29 |
+
<a href="/projects/all" class="inline-flex items-center rounded-2xl bg-violet-700 px-5 py-3 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-600">
|
| 30 |
<i data-lucide="folder-kanban" class="mr-2 h-4 w-4"></i>
|
| 31 |
All Projects
|
| 32 |
</a>
|
|
|
|
| 40 |
|
| 41 |
@if (isLoading)
|
| 42 |
{
|
| 43 |
+
<div class="rounded-xl border border-slate-200 bg-white px-6 py-12 text-center shadow-sm">
|
| 44 |
<div class="mx-auto flex items-center justify-center gap-3 text-slate-500">
|
| 45 |
<svg class="h-5 w-5 animate-spin" viewBox="0 0 24 24">
|
| 46 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
|
|
| 52 |
}
|
| 53 |
else if (!string.IsNullOrWhiteSpace(errorMessage))
|
| 54 |
{
|
| 55 |
+
<div class="rounded-xl border border-rose-200 bg-rose-50 px-6 py-5 text-sm text-rose-700 shadow-sm">
|
| 56 |
@errorMessage
|
| 57 |
</div>
|
| 58 |
}
|
|
|
|
| 61 |
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
| 62 |
@foreach (var metric in SummaryMetrics)
|
| 63 |
{
|
| 64 |
+
<div class="group relative overflow-hidden rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-transform duration-200 hover:-translate-y-1">
|
| 65 |
<div class="relative flex items-center justify-between">
|
| 66 |
<div class="@metric.IconWrapClass">
|
| 67 |
<i data-lucide="@metric.Icon" class="h-5 w-5"></i>
|
|
|
|
| 75 |
</div>
|
| 76 |
|
| 77 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.7fr)_minmax(340px,1fr)]">
|
| 78 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
| 79 |
<div class="flex items-start justify-between gap-4">
|
| 80 |
<div>
|
| 81 |
+
<h3 class="text-xl font-bold text-slate-950">Weekly Task Progress</h3>
|
| 82 |
<p class="mt-1 text-sm text-slate-500">Completed vs pending tasks per week</p>
|
| 83 |
</div>
|
| 84 |
<div class="text-sm text-slate-500">@WeeklyRangeLabel</div>
|
|
|
|
| 103 |
<g @onmouseenter="() => SetHoveredWeek(index)" @onmouseleave="ClearHoveredWeek">
|
| 104 |
<rect x="@week.HoverX" y="40" width="@week.HoverWidth" height="160" rx="18" fill="@(hoveredWeekIndex == index ? "#f8fafc" : "transparent")" opacity="@(hoveredWeekIndex == index ? "1" : "0")" />
|
| 105 |
|
| 106 |
+
<rect x="@week.CompletedX" y="@(week.BaseY - week.CompletedHeight)" width="@BarWidth" height="@week.CompletedHeight" rx="8" fill="#3b82f6" />
|
| 107 |
<rect x="@week.PendingX" y="@(week.BaseY - week.PendingHeight)" width="@BarWidth" height="@week.PendingHeight" rx="8" fill="#14b8a6" />
|
| 108 |
|
| 109 |
<text x="@week.LabelX" y="246" text-anchor="middle" font-size="13" fill="#64748b">@week.Label</text>
|
|
|
|
| 113 |
|
| 114 |
@if (hoveredWeek is not null)
|
| 115 |
{
|
| 116 |
+
<div class="pointer-events-none absolute z-20 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-lg"
|
| 117 |
style="@HoveredWeekTooltipStyle">
|
| 118 |
<div class="text-sm font-semibold text-slate-950">@hoveredWeek.Label</div>
|
| 119 |
<div class="mt-2 text-sm text-violet-700">Completed: @hoveredWeek.Completed</div>
|
|
|
|
| 139 |
</div>
|
| 140 |
</div>
|
| 141 |
|
| 142 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
| 143 |
+
<h3 class="text-xl font-bold text-slate-950">Team Progress</h3>
|
| 144 |
<p class="mt-1 text-sm text-slate-500">Members with active workload</p>
|
| 145 |
|
| 146 |
<div class="mt-6 max-h-[460px] space-y-4 overflow-y-auto pr-1">
|
|
|
|
| 181 |
</div>
|
| 182 |
|
| 183 |
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1.05fr)_minmax(0,0.95fr)]">
|
| 184 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
| 185 |
<div class="flex items-start justify-between gap-4">
|
| 186 |
<div>
|
| 187 |
+
<h3 class="text-xl font-bold text-slate-950">@CalendarMonthLabel</h3>
|
| 188 |
<p class="mt-1 text-sm text-slate-500">@CalendarTodayLabel</p>
|
| 189 |
</div>
|
| 190 |
</div>
|
|
|
|
| 214 |
</div>
|
| 215 |
</div>
|
| 216 |
|
| 217 |
+
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
| 218 |
+
<h3 class="text-xl font-bold text-slate-950">Upcoming Deadlines</h3>
|
| 219 |
<p class="mt-1 text-sm text-slate-500">Projects ending soon</p>
|
| 220 |
|
| 221 |
<div class="mt-6 max-h-[460px] space-y-3 overflow-y-auto pr-1">
|
|
|
|
| 243 |
</div>
|
| 244 |
</div>
|
| 245 |
|
| 246 |
+
<div class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
| 247 |
<div class="flex items-center justify-between gap-4 border-b border-slate-100 px-6 py-5">
|
| 248 |
<div>
|
| 249 |
+
<h3 class="text-xl font-bold text-slate-950">My Projects</h3>
|
| 250 |
<p class="mt-1 text-sm text-slate-500">Click "View Tasks" to drill in</p>
|
| 251 |
</div>
|
| 252 |
<a href="/projects/all" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-violet-700 shadow-sm transition-colors hover:bg-slate-50">
|
|
|
|
| 281 |
|
| 282 |
<div class="flex flex-col items-start gap-3 lg:items-end">
|
| 283 |
<div class="text-right">
|
| 284 |
+
<div class="text-xl font-bold text-slate-950">@project.ProgressPercentage.ToString("F0")%</div>
|
| 285 |
+
<div class="text-xs uppercase tracking-[0.08em] text-slate-500">Complete</div>
|
| 286 |
</div>
|
| 287 |
<button @onclick="() => ViewProjectTasks(project.Id)" class="inline-flex items-center rounded-2xl bg-violet-700 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-600">
|
| 288 |
<i data-lucide="eye" class="mr-2 h-4 w-4"></i>
|
|
|
|
| 449 |
private List<ProjectCardViewModel> BuildProjectCards()
|
| 450 |
{
|
| 451 |
var progressByProject = projectProgress.ToDictionary(item => item.ProjectId, item => item);
|
| 452 |
+
var palette = new[] { "#2563eb", "#0ea5e9", "#10b981", "#f59e0b", "#f97316" };
|
| 453 |
|
| 454 |
return projects
|
| 455 |
.OrderByDescending(project => progressByProject.TryGetValue(project.Id, out var progress) ? progress.CompletionPercentage : 0)
|
|
|
|
| 494 |
|
| 495 |
private List<TeamProgressViewModel> BuildTeamProgress()
|
| 496 |
{
|
| 497 |
+
var palette = new[] { "#2563eb", "#0ea5e9", "#10b981", "#f59e0b", "#f97316", "#ec4899" };
|
| 498 |
|
| 499 |
return users
|
| 500 |
.Select((user, index) =>
|
|
|
|
| 695 |
{
|
| 696 |
var palette = new[]
|
| 697 |
{
|
| 698 |
+
"#2563eb",
|
| 699 |
"#0ea5e9",
|
| 700 |
"#10b981",
|
| 701 |
"#f59e0b",
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Issues/AddIssue.razor
CHANGED
|
@@ -129,7 +129,7 @@
|
|
| 129 |
|
| 130 |
@if (isAssigneeDropdownOpen)
|
| 131 |
{
|
| 132 |
-
<div class="absolute bottom-full left-0 z-30 mb-2 w-full rounded-2xl border border-slate-200 bg-white p-3 shadow-
|
| 133 |
<div class="flex items-center justify-end">
|
| 134 |
<button type="button"
|
| 135 |
@onclick="CloseAssigneeDropdown"
|
|
|
|
| 129 |
|
| 130 |
@if (isAssigneeDropdownOpen)
|
| 131 |
{
|
| 132 |
+
<div class="absolute bottom-full left-0 z-30 mb-2 w-full rounded-2xl border border-slate-200 bg-white p-3 shadow-lg">
|
| 133 |
<div class="flex items-center justify-end">
|
| 134 |
<button type="button"
|
| 135 |
@onclick="CloseAssigneeDropdown"
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Issues/Issues.razor
CHANGED
|
@@ -200,7 +200,7 @@
|
|
| 200 |
@if (showModal)
|
| 201 |
{
|
| 202 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 203 |
-
<div class="bg-white rounded-2xl shadow-
|
| 204 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 205 |
<div>
|
| 206 |
<h3 class="text-lg font-semibold text-slate-900">Edit Issue</h3>
|
|
|
|
| 200 |
@if (showModal)
|
| 201 |
{
|
| 202 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 203 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-2xl mx-4 overflow-hidden">
|
| 204 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 205 |
<div>
|
| 206 |
<h3 class="text-lg font-semibold text-slate-900">Edit Issue</h3>
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Projects/ProjectAssign.razor
CHANGED
|
@@ -90,7 +90,7 @@
|
|
| 90 |
else
|
| 91 |
{
|
| 92 |
<!-- Selected Project Header -->
|
| 93 |
-
<div class="p-6 border-b border-slate-100 bg-
|
| 94 |
<div>
|
| 95 |
<span class="text-[10px] font-bold text-violet-600 uppercase tracking-wider">Currently Managing</span>
|
| 96 |
<h3 class="text-xl font-bold text-slate-900">@SelectedProject.Name</h3>
|
|
|
|
| 90 |
else
|
| 91 |
{
|
| 92 |
<!-- Selected Project Header -->
|
| 93 |
+
<div class="p-6 border-b border-slate-100 bg-slate-50 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
| 94 |
<div>
|
| 95 |
<span class="text-[10px] font-bold text-violet-600 uppercase tracking-wider">Currently Managing</span>
|
| 96 |
<h3 class="text-xl font-bold text-slate-900">@SelectedProject.Name</h3>
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Projects/Projects.razor
CHANGED
|
@@ -132,7 +132,7 @@
|
|
| 132 |
@if (showModal)
|
| 133 |
{
|
| 134 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 135 |
-
<div class="bg-white rounded-2xl shadow-
|
| 136 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 137 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit Project" : "New Project")</h3>
|
| 138 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
@@ -209,8 +209,8 @@
|
|
| 209 |
@if (showAssignModal)
|
| 210 |
{
|
| 211 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 212 |
-
<div class="bg-white rounded-2xl shadow-
|
| 213 |
-
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-
|
| 214 |
<div>
|
| 215 |
<h3 class="text-lg font-semibold text-slate-900">Assign Members</h3>
|
| 216 |
<p class="text-xs text-slate-500 mt-0.5">Project: <span class="font-semibold text-violet-700">@assigningProjectName</span></p>
|
|
|
|
| 132 |
@if (showModal)
|
| 133 |
{
|
| 134 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 135 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-lg mx-4 overflow-hidden">
|
| 136 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 137 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit Project" : "New Project")</h3>
|
| 138 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
|
|
| 209 |
@if (showAssignModal)
|
| 210 |
{
|
| 211 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 212 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-lg mx-4 overflow-hidden">
|
| 213 |
+
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-slate-50">
|
| 214 |
<div>
|
| 215 |
<h3 class="text-lg font-semibold text-slate-900">Assign Members</h3>
|
| 216 |
<p class="text-xs text-slate-500 mt-0.5">Project: <span class="font-semibold text-violet-700">@assigningProjectName</span></p>
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Roles/Roles.razor
CHANGED
|
@@ -83,7 +83,7 @@
|
|
| 83 |
<span class="text-sm font-semibold text-slate-900">@role.Name</span>
|
| 84 |
</div>
|
| 85 |
</td>
|
| 86 |
-
<td class="px-6 py-4 text-sm text-slate-500 max-w-xs truncate">@(role.Description ?? "
|
| 87 |
<td class="px-6 py-4 text-sm text-slate-400">@DisplayFormats.Date(role.CreatedAt)</td>
|
| 88 |
<td class="px-6 py-4 text-right">
|
| 89 |
<div class="flex items-center justify-end space-x-2">
|
|
@@ -135,11 +135,11 @@
|
|
| 135 |
@if (showRoleModal)
|
| 136 |
{
|
| 137 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 138 |
-
<div class="bg-white rounded-2xl shadow-
|
| 139 |
style="max-width: @(isEditing ? "30rem" : "58rem"); max-height: 88vh;">
|
| 140 |
|
| 141 |
<!-- Header -->
|
| 142 |
-
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-
|
| 143 |
<div class="flex items-center gap-3">
|
| 144 |
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-600 text-white">
|
| 145 |
<i data-lucide="@(isEditing ? "pencil" : "shield-plus")" class="h-4 w-4"></i>
|
|
@@ -255,7 +255,7 @@
|
|
| 255 |
var parentChecked = formSelectedAccessCodes.Contains(parent.MenuCode);
|
| 256 |
|
| 257 |
<div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
|
| 258 |
-
<label class="flex items-start gap-3 px-4 py-3 bg-
|
| 259 |
<input type="checkbox" checked="@parentChecked"
|
| 260 |
@onchange="(e) => ToggleAccessSelection(parent, (bool)(e.Value ?? false), isForm: true)"
|
| 261 |
class="mt-0.5 h-4 w-4 rounded border-slate-300 accent-violet-600" />
|
|
@@ -387,16 +387,16 @@
|
|
| 387 |
@if (showAccessModal)
|
| 388 |
{
|
| 389 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 390 |
-
<div class="bg-white rounded-2xl shadow-
|
| 391 |
|
| 392 |
<!-- Header -->
|
| 393 |
-
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-
|
| 394 |
<div class="flex items-center gap-3">
|
| 395 |
<div class="flex h-9 w-9 items-center justify-center rounded-lg bg-violet-100 text-violet-600">
|
| 396 |
<i data-lucide="shield-check" class="h-4 w-4"></i>
|
| 397 |
</div>
|
| 398 |
<div>
|
| 399 |
-
<h3 class="text-base font-semibold text-slate-900">Role Access
|
| 400 |
<p class="text-xs text-slate-500">Configure access and permissions</p>
|
| 401 |
</div>
|
| 402 |
</div>
|
|
@@ -459,7 +459,7 @@
|
|
| 459 |
var parentChecked = selectedAccessCodes.Contains(parent.MenuCode);
|
| 460 |
|
| 461 |
<div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
|
| 462 |
-
<label class="flex items-start gap-3 px-4 py-3 bg-
|
| 463 |
<input type="checkbox" checked="@parentChecked"
|
| 464 |
@onchange="(e) => ToggleAccessSelection(parent, (bool)(e.Value ?? false), isForm: false)"
|
| 465 |
class="mt-0.5 h-4 w-4 rounded border-slate-300 accent-violet-600" />
|
|
|
|
| 83 |
<span class="text-sm font-semibold text-slate-900">@role.Name</span>
|
| 84 |
</div>
|
| 85 |
</td>
|
| 86 |
+
<td class="px-6 py-4 text-sm text-slate-500 max-w-xs truncate">@(role.Description ?? "-")</td>
|
| 87 |
<td class="px-6 py-4 text-sm text-slate-400">@DisplayFormats.Date(role.CreatedAt)</td>
|
| 88 |
<td class="px-6 py-4 text-right">
|
| 89 |
<div class="flex items-center justify-end space-x-2">
|
|
|
|
| 135 |
@if (showRoleModal)
|
| 136 |
{
|
| 137 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 138 |
+
<div class="bg-white rounded-2xl shadow-lg w-full mx-4 flex flex-col overflow-hidden"
|
| 139 |
style="max-width: @(isEditing ? "30rem" : "58rem"); max-height: 88vh;">
|
| 140 |
|
| 141 |
<!-- Header -->
|
| 142 |
+
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-slate-50 shrink-0">
|
| 143 |
<div class="flex items-center gap-3">
|
| 144 |
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-600 text-white">
|
| 145 |
<i data-lucide="@(isEditing ? "pencil" : "shield-plus")" class="h-4 w-4"></i>
|
|
|
|
| 255 |
var parentChecked = formSelectedAccessCodes.Contains(parent.MenuCode);
|
| 256 |
|
| 257 |
<div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
|
| 258 |
+
<label class="flex items-start gap-3 px-4 py-3 bg-slate-50 cursor-pointer hover:bg-slate-50 transition-colors">
|
| 259 |
<input type="checkbox" checked="@parentChecked"
|
| 260 |
@onchange="(e) => ToggleAccessSelection(parent, (bool)(e.Value ?? false), isForm: true)"
|
| 261 |
class="mt-0.5 h-4 w-4 rounded border-slate-300 accent-violet-600" />
|
|
|
|
| 387 |
@if (showAccessModal)
|
| 388 |
{
|
| 389 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 390 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-6xl mx-4 overflow-hidden flex flex-col" style="max-height: 88vh;">
|
| 391 |
|
| 392 |
<!-- Header -->
|
| 393 |
+
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-slate-50">
|
| 394 |
<div class="flex items-center gap-3">
|
| 395 |
<div class="flex h-9 w-9 items-center justify-center rounded-lg bg-violet-100 text-violet-600">
|
| 396 |
<i data-lucide="shield-check" class="h-4 w-4"></i>
|
| 397 |
</div>
|
| 398 |
<div>
|
| 399 |
+
<h3 class="text-base font-semibold text-slate-900">Role Access - <span class="text-violet-700">@selectedRoleName</span></h3>
|
| 400 |
<p class="text-xs text-slate-500">Configure access and permissions</p>
|
| 401 |
</div>
|
| 402 |
</div>
|
|
|
|
| 459 |
var parentChecked = selectedAccessCodes.Contains(parent.MenuCode);
|
| 460 |
|
| 461 |
<div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
|
| 462 |
+
<label class="flex items-start gap-3 px-4 py-3 bg-slate-50 cursor-pointer hover:bg-slate-50 transition-colors">
|
| 463 |
<input type="checkbox" checked="@parentChecked"
|
| 464 |
@onchange="(e) => ToggleAccessSelection(parent, (bool)(e.Value ?? false), isForm: false)"
|
| 465 |
class="mt-0.5 h-4 w-4 rounded border-slate-300 accent-violet-600" />
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/KanbanBoard.razor
CHANGED
|
@@ -145,7 +145,7 @@
|
|
| 145 |
@if (showTaskModal)
|
| 146 |
{
|
| 147 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 148 |
-
<div class="bg-white rounded-2xl shadow-
|
| 149 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 150 |
<h3 class="text-lg font-semibold text-slate-900">New Task</h3>
|
| 151 |
<button @onclick="CloseTaskModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
@@ -607,7 +607,7 @@
|
|
| 607 |
TaskPriority.Low => "Low",
|
| 608 |
TaskPriority.Medium => "Medium",
|
| 609 |
TaskPriority.High => "High",
|
| 610 |
-
_ => "
|
| 611 |
};
|
| 612 |
|
| 613 |
private record KanbanColumn(AppTaskStatus StatusId, string Name, string DotColor);
|
|
|
|
| 145 |
@if (showTaskModal)
|
| 146 |
{
|
| 147 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 148 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-lg mx-4 overflow-hidden">
|
| 149 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 150 |
<h3 class="text-lg font-semibold text-slate-900">New Task</h3>
|
| 151 |
<button @onclick="CloseTaskModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
|
|
| 607 |
TaskPriority.Low => "Low",
|
| 608 |
TaskPriority.Medium => "Medium",
|
| 609 |
TaskPriority.High => "High",
|
| 610 |
+
_ => "-"
|
| 611 |
};
|
| 612 |
|
| 613 |
private record KanbanColumn(AppTaskStatus StatusId, string Name, string DotColor);
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/TaskAssign.razor
CHANGED
|
@@ -122,7 +122,7 @@
|
|
| 122 |
else
|
| 123 |
{
|
| 124 |
<!-- Selected Task Details -->
|
| 125 |
-
<div class="p-6 border-b border-slate-100 bg-
|
| 126 |
<div class="flex items-center gap-2 mb-2">
|
| 127 |
<span class="text-[10px] font-bold text-violet-600 uppercase tracking-wider">Currently Assigning Task</span>
|
| 128 |
<span class="text-[10px] font-mono font-semibold text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded">TASK-#@SelectedTask.Id</span>
|
|
@@ -552,6 +552,6 @@
|
|
| 552 |
TaskPriority.Low => "Low",
|
| 553 |
TaskPriority.Medium => "Medium",
|
| 554 |
TaskPriority.High => "High",
|
| 555 |
-
_ => "
|
| 556 |
};
|
| 557 |
}
|
|
|
|
| 122 |
else
|
| 123 |
{
|
| 124 |
<!-- Selected Task Details -->
|
| 125 |
+
<div class="p-6 border-b border-slate-100 bg-slate-50">
|
| 126 |
<div class="flex items-center gap-2 mb-2">
|
| 127 |
<span class="text-[10px] font-bold text-violet-600 uppercase tracking-wider">Currently Assigning Task</span>
|
| 128 |
<span class="text-[10px] font-mono font-semibold text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded">TASK-#@SelectedTask.Id</span>
|
|
|
|
| 552 |
TaskPriority.Low => "Low",
|
| 553 |
TaskPriority.Medium => "Medium",
|
| 554 |
TaskPriority.High => "High",
|
| 555 |
+
_ => "-"
|
| 556 |
};
|
| 557 |
}
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/TaskDetails.razor
CHANGED
|
@@ -81,7 +81,7 @@
|
|
| 81 |
}
|
| 82 |
else
|
| 83 |
{
|
| 84 |
-
<h2 class="text-
|
| 85 |
@task.Title
|
| 86 |
@if (hasTaskManagePermission)
|
| 87 |
{
|
|
@@ -661,7 +661,7 @@
|
|
| 661 |
}
|
| 662 |
}
|
| 663 |
|
| 664 |
-
//
|
| 665 |
|
| 666 |
private async Task ExecuteConfirmAction()
|
| 667 |
{
|
|
@@ -679,7 +679,7 @@
|
|
| 679 |
confirmAction = null;
|
| 680 |
}
|
| 681 |
|
| 682 |
-
//
|
| 683 |
|
| 684 |
private string GetStatusLabel(AppTaskStatus statusId) => statusId switch
|
| 685 |
{
|
|
|
|
| 81 |
}
|
| 82 |
else
|
| 83 |
{
|
| 84 |
+
<h2 class="text-xl font-bold text-slate-900 leading-tight flex items-center gap-2 group">
|
| 85 |
@task.Title
|
| 86 |
@if (hasTaskManagePermission)
|
| 87 |
{
|
|
|
|
| 661 |
}
|
| 662 |
}
|
| 663 |
|
| 664 |
+
// --- CONFIRM DIALOG LOGIC -------------------------------------------------
|
| 665 |
|
| 666 |
private async Task ExecuteConfirmAction()
|
| 667 |
{
|
|
|
|
| 679 |
confirmAction = null;
|
| 680 |
}
|
| 681 |
|
| 682 |
+
// --- DISPLAY UTILITIES ----------------------------------------------------
|
| 683 |
|
| 684 |
private string GetStatusLabel(AppTaskStatus statusId) => statusId switch
|
| 685 |
{
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Tasks/Tasks.razor
CHANGED
|
@@ -194,7 +194,7 @@
|
|
| 194 |
@if (showModal)
|
| 195 |
{
|
| 196 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 197 |
-
<div class="bg-white rounded-2xl shadow-
|
| 198 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 199 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit Task" : "New Task")</h3>
|
| 200 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
@@ -271,7 +271,7 @@
|
|
| 271 |
|
| 272 |
@if (isAssigneeDropdownOpen)
|
| 273 |
{
|
| 274 |
-
<div class="absolute bottom-full left-0 z-30 mb-2 w-full rounded-2xl border border-slate-200 bg-white p-3 shadow-
|
| 275 |
<div class="flex items-center justify-end">
|
| 276 |
<button type="button"
|
| 277 |
@onclick="CloseAssigneeDropdown"
|
|
@@ -994,7 +994,7 @@
|
|
| 994 |
TaskPriority.Low => "Low",
|
| 995 |
TaskPriority.Medium => "Medium",
|
| 996 |
TaskPriority.High => "High",
|
| 997 |
-
_ => "
|
| 998 |
};
|
| 999 |
|
| 1000 |
}
|
|
|
|
| 194 |
@if (showModal)
|
| 195 |
{
|
| 196 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 197 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-lg mx-4 overflow-hidden">
|
| 198 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 199 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit Task" : "New Task")</h3>
|
| 200 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
|
|
| 271 |
|
| 272 |
@if (isAssigneeDropdownOpen)
|
| 273 |
{
|
| 274 |
+
<div class="absolute bottom-full left-0 z-30 mb-2 w-full rounded-2xl border border-slate-200 bg-white p-3 shadow-lg">
|
| 275 |
<div class="flex items-center justify-end">
|
| 276 |
<button type="button"
|
| 277 |
@onclick="CloseAssigneeDropdown"
|
|
|
|
| 994 |
TaskPriority.Low => "Low",
|
| 995 |
TaskPriority.Medium => "Medium",
|
| 996 |
TaskPriority.High => "High",
|
| 997 |
+
_ => "-"
|
| 998 |
};
|
| 999 |
|
| 1000 |
}
|
TaskTrackingSystem.WebApp/Components/Pages/Features/Users/Users.razor
CHANGED
|
@@ -104,7 +104,7 @@
|
|
| 104 |
</div>
|
| 105 |
</td>
|
| 106 |
<td class="px-6 py-4 text-sm text-slate-600">@user.Email</td>
|
| 107 |
-
<td class="px-6 py-4 text-sm text-slate-500">@(user.Phone ?? "
|
| 108 |
<td class="px-6 py-4">
|
| 109 |
@{ var role = roles.FirstOrDefault(r => r.Id == user.RoleId); }
|
| 110 |
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-50 text-indigo-700">
|
|
@@ -157,7 +157,7 @@
|
|
| 157 |
@if (showModal)
|
| 158 |
{
|
| 159 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 160 |
-
<div class="bg-white rounded-2xl shadow-
|
| 161 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 162 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit User" : "New User")</h3>
|
| 163 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
|
|
|
| 104 |
</div>
|
| 105 |
</td>
|
| 106 |
<td class="px-6 py-4 text-sm text-slate-600">@user.Email</td>
|
| 107 |
+
<td class="px-6 py-4 text-sm text-slate-500">@(user.Phone ?? "-")</td>
|
| 108 |
<td class="px-6 py-4">
|
| 109 |
@{ var role = roles.FirstOrDefault(r => r.Id == user.RoleId); }
|
| 110 |
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-50 text-indigo-700">
|
|
|
|
| 157 |
@if (showModal)
|
| 158 |
{
|
| 159 |
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 160 |
+
<div class="bg-white rounded-2xl shadow-lg w-full max-w-lg mx-4 overflow-hidden">
|
| 161 |
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
| 162 |
<h3 class="text-lg font-semibold text-slate-900">@(isEditing ? "Edit User" : "New User")</h3>
|
| 163 |
<button @onclick="CloseModal" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-all">
|
TaskTrackingSystem.WebApp/Components/Partial/ConfirmDialog.razor
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
@if (IsVisible)
|
| 2 |
{
|
| 3 |
<div class="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 4 |
-
<div class="
|
| 5 |
<div class="p-6 text-center">
|
| 6 |
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full mb-4" style="background-color: @GetIconBg(); color: @GetIconColor();">
|
| 7 |
<i data-lucide="@Icon" class="h-7 w-7"></i>
|
|
@@ -16,7 +16,7 @@
|
|
| 16 |
Cancel
|
| 17 |
</button>
|
| 18 |
}
|
| 19 |
-
<button @onclick="OnConfirm" class="flex-1 rounded-lg px-4 py-2.5 text-sm font-medium text-white transition-colors" style="
|
| 20 |
@ConfirmText
|
| 21 |
</button>
|
| 22 |
</div>
|
|
@@ -37,24 +37,28 @@
|
|
| 37 |
[Parameter] public EventCallback OnConfirm { get; set; }
|
| 38 |
[Parameter] public EventCallback OnCancel { get; set; }
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
private string GetIconBg()
|
| 41 |
{
|
| 42 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 43 |
-
return "var(--status-
|
| 44 |
return "var(--status-success-bg)";
|
| 45 |
}
|
| 46 |
|
| 47 |
private string GetIconColor()
|
| 48 |
{
|
| 49 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 50 |
-
return "var(--
|
| 51 |
return "var(--brand-primary)";
|
| 52 |
}
|
| 53 |
|
| 54 |
private string GetButtonBg()
|
| 55 |
{
|
| 56 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 57 |
-
return "var(--
|
| 58 |
return "var(--brand-primary)";
|
| 59 |
}
|
| 60 |
}
|
|
|
|
| 1 |
@if (IsVisible)
|
| 2 |
{
|
| 3 |
<div class="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
| 4 |
+
<div class="w-full max-w-sm mx-4 overflow-hidden rounded-lg border border-slate-100 bg-white shadow-lg animate-in">
|
| 5 |
<div class="p-6 text-center">
|
| 6 |
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full mb-4" style="background-color: @GetIconBg(); color: @GetIconColor();">
|
| 7 |
<i data-lucide="@Icon" class="h-7 w-7"></i>
|
|
|
|
| 16 |
Cancel
|
| 17 |
</button>
|
| 18 |
}
|
| 19 |
+
<button @onclick="OnConfirm" class="flex-1 rounded-lg px-4 py-2.5 text-sm font-medium text-white transition-colors @ButtonClass" style="@ConfirmButtonStyle">
|
| 20 |
@ConfirmText
|
| 21 |
</button>
|
| 22 |
</div>
|
|
|
|
| 37 |
[Parameter] public EventCallback OnConfirm { get; set; }
|
| 38 |
[Parameter] public EventCallback OnCancel { get; set; }
|
| 39 |
|
| 40 |
+
private string ConfirmButtonStyle => string.IsNullOrWhiteSpace(ButtonClass)
|
| 41 |
+
? $"background-color: {GetButtonBg()};"
|
| 42 |
+
: string.Empty;
|
| 43 |
+
|
| 44 |
private string GetIconBg()
|
| 45 |
{
|
| 46 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 47 |
+
return "var(--status-danger-bg)";
|
| 48 |
return "var(--status-success-bg)";
|
| 49 |
}
|
| 50 |
|
| 51 |
private string GetIconColor()
|
| 52 |
{
|
| 53 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 54 |
+
return "var(--status-danger-text)";
|
| 55 |
return "var(--brand-primary)";
|
| 56 |
}
|
| 57 |
|
| 58 |
private string GetButtonBg()
|
| 59 |
{
|
| 60 |
if (Icon.Contains("trash") || Icon.Contains("alert"))
|
| 61 |
+
return "var(--status-danger-text)";
|
| 62 |
return "var(--brand-primary)";
|
| 63 |
}
|
| 64 |
}
|
TaskTrackingSystem.WebApp/Components/Partial/EmptyState.razor
CHANGED
|
@@ -2,9 +2,11 @@
|
|
| 2 |
|
| 3 |
<tr>
|
| 4 |
<td colspan="@ColSpan" class="px-6 py-12 text-center">
|
| 5 |
-
<
|
| 6 |
-
|
| 7 |
-
<
|
|
|
|
|
|
|
| 8 |
</td>
|
| 9 |
</tr>
|
| 10 |
|
|
|
|
| 2 |
|
| 3 |
<tr>
|
| 4 |
<td colspan="@ColSpan" class="px-6 py-12 text-center">
|
| 5 |
+
<div class="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-slate-100 text-slate-400">
|
| 6 |
+
<i data-lucide="@Icon" class="h-6 w-6"></i>
|
| 7 |
+
</div>
|
| 8 |
+
<p class="text-sm font-semibold text-slate-700">@Title</p>
|
| 9 |
+
<p class="mx-auto mt-1 max-w-sm text-xs leading-5 text-slate-500">@Subtitle</p>
|
| 10 |
</td>
|
| 11 |
</tr>
|
| 12 |
|
TaskTrackingSystem.WebApp/Components/Partial/LanguageToggle.razor
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
|
| 5 |
<button type="button"
|
| 6 |
@onclick="ToggleLanguage"
|
| 7 |
-
class="
|
| 8 |
title="@ButtonTitle"
|
| 9 |
aria-label="@ButtonTitle">
|
| 10 |
<span class="text-xs font-semibold tracking-wide">@ButtonLabel</span>
|
|
|
|
| 4 |
|
| 5 |
<button type="button"
|
| 6 |
@onclick="ToggleLanguage"
|
| 7 |
+
class="control-button px-3"
|
| 8 |
title="@ButtonTitle"
|
| 9 |
aria-label="@ButtonTitle">
|
| 10 |
<span class="text-xs font-semibold tracking-wide">@ButtonLabel</span>
|
TaskTrackingSystem.WebApp/Components/Partial/LoadingSpinner.razor
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
@* Partial/LoadingSpinner.razor - Reusable loading state component *@
|
| 2 |
|
| 3 |
-
<div class="flex items-center justify-center py-
|
| 4 |
<div class="flex items-center space-x-3 text-slate-500">
|
| 5 |
<svg class="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
| 6 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
|
|
| 1 |
@* Partial/LoadingSpinner.razor - Reusable loading state component *@
|
| 2 |
|
| 3 |
+
<div class="surface-panel flex items-center justify-center py-14">
|
| 4 |
<div class="flex items-center space-x-3 text-slate-500">
|
| 5 |
<svg class="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
| 6 |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
TaskTrackingSystem.WebApp/Components/Partial/NotificationBell.razor
CHANGED
|
@@ -8,11 +8,11 @@
|
|
| 8 |
<div class="relative">
|
| 9 |
<button type="button"
|
| 10 |
@onclick="ToggleOpenAsync"
|
| 11 |
-
class="
|
| 12 |
<i data-lucide="bell" class="h-5 w-5"></i>
|
| 13 |
@if (UnreadCount > 0)
|
| 14 |
{
|
| 15 |
-
<span class="absolute right-1 top-1 min-w-4
|
| 16 |
@(UnreadCount > 99 ? "99+" : UnreadCount.ToString())
|
| 17 |
</span>
|
| 18 |
}
|
|
@@ -20,8 +20,8 @@
|
|
| 20 |
|
| 21 |
@if (IsOpen)
|
| 22 |
{
|
| 23 |
-
<div class="absolute right-0 mt-3 w-[380px] max-w-[calc(100vw-2rem)] rounded-
|
| 24 |
-
<div class="flex items-center justify-between px-4 py-3 border-b border-slate-100 bg-slate-50">
|
| 25 |
<div>
|
| 26 |
<p class="text-sm font-semibold text-slate-900">Notifications</p>
|
| 27 |
<p class="text-xs text-slate-500">@UnreadCount unread</p>
|
|
@@ -62,10 +62,10 @@
|
|
| 62 |
@foreach (var item in Notifications)
|
| 63 |
{
|
| 64 |
<button type="button"
|
| 65 |
-
class="w-full
|
| 66 |
@onclick="() => OpenNotificationAsync(item)">
|
| 67 |
<div class="flex items-start gap-3">
|
| 68 |
-
<div class="mt-0.5 h-9 w-9
|
| 69 |
<i data-lucide="bell-ring" class="h-4 w-4"></i>
|
| 70 |
</div>
|
| 71 |
<div class="min-w-0 flex-1">
|
|
|
|
| 8 |
<div class="relative">
|
| 9 |
<button type="button"
|
| 10 |
@onclick="ToggleOpenAsync"
|
| 11 |
+
class="control-button relative">
|
| 12 |
<i data-lucide="bell" class="h-5 w-5"></i>
|
| 13 |
@if (UnreadCount > 0)
|
| 14 |
{
|
| 15 |
+
<span class="absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-rose-600 px-1 text-[10px] font-bold text-white">
|
| 16 |
@(UnreadCount > 99 ? "99+" : UnreadCount.ToString())
|
| 17 |
</span>
|
| 18 |
}
|
|
|
|
| 20 |
|
| 21 |
@if (IsOpen)
|
| 22 |
{
|
| 23 |
+
<div class="absolute right-0 z-50 mt-3 w-[380px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-lg border border-slate-200 bg-white shadow-lg">
|
| 24 |
+
<div class="flex items-center justify-between px-4 py-3 border-b border-slate-100 bg-slate-50/80">
|
| 25 |
<div>
|
| 26 |
<p class="text-sm font-semibold text-slate-900">Notifications</p>
|
| 27 |
<p class="text-xs text-slate-500">@UnreadCount unread</p>
|
|
|
|
| 62 |
@foreach (var item in Notifications)
|
| 63 |
{
|
| 64 |
<button type="button"
|
| 65 |
+
class="w-full border-b border-slate-100 px-4 py-3 text-left transition-colors hover:bg-slate-50 @(item.IsRead ? "bg-white" : "bg-violet-50/70")"
|
| 66 |
@onclick="() => OpenNotificationAsync(item)">
|
| 67 |
<div class="flex items-start gap-3">
|
| 68 |
+
<div class="mt-0.5 flex h-9 w-9 items-center justify-center rounded-full @(item.IsRead ? "bg-slate-100 text-slate-500" : "bg-violet-100 text-violet-700")">
|
| 69 |
<i data-lucide="bell-ring" class="h-4 w-4"></i>
|
| 70 |
</div>
|
| 71 |
<div class="min-w-0 flex-1">
|
TaskTrackingSystem.WebApp/Components/Partial/Pagination.razor
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
@* Partial/Pagination.razor - Reusable pagination component *@
|
| 2 |
|
| 3 |
-
<div class="flex flex-wrap items-center justify-between gap-3
|
| 4 |
<div class="text-xs text-slate-500">
|
| 5 |
@if (TotalCount == 0)
|
| 6 |
{
|
|
@@ -18,7 +18,7 @@
|
|
| 18 |
<div class="flex items-center gap-2 text-xs text-slate-500">
|
| 19 |
<span>Rows per page</span>
|
| 20 |
<select value="@PageSize" @onchange="HandlePageSizeChange"
|
| 21 |
-
class="rounded-
|
| 22 |
<option value="5">5</option>
|
| 23 |
<option value="10">10</option>
|
| 24 |
<option value="20">20</option>
|
|
@@ -28,25 +28,21 @@
|
|
| 28 |
|
| 29 |
<div class="flex items-center gap-1">
|
| 30 |
<button @onclick="() => GoToPage(1)" disabled="@(CurrentPage <= 1)"
|
| 31 |
-
class="inline-flex h-
|
| 32 |
title="First page">
|
| 33 |
-
<
|
| 34 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M11 19l-7-7 7-7M18 19l-7-7 7-7" />
|
| 35 |
-
</svg>
|
| 36 |
</button>
|
| 37 |
<button @onclick="() => GoToPage(CurrentPage - 1)" disabled="@(CurrentPage <= 1)"
|
| 38 |
-
class="inline-flex h-
|
| 39 |
title="Previous page">
|
| 40 |
-
<
|
| 41 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
| 42 |
-
</svg>
|
| 43 |
</button>
|
| 44 |
|
| 45 |
@foreach (var p in GetVisiblePages())
|
| 46 |
{
|
| 47 |
if (p == -1)
|
| 48 |
{
|
| 49 |
-
<span class="inline-flex h-
|
| 50 |
}
|
| 51 |
else
|
| 52 |
{
|
|
@@ -58,18 +54,14 @@
|
|
| 58 |
}
|
| 59 |
|
| 60 |
<button @onclick="() => GoToPage(CurrentPage + 1)" disabled="@(CurrentPage >= TotalPages)"
|
| 61 |
-
class="inline-flex h-
|
| 62 |
title="Next page">
|
| 63 |
-
<
|
| 64 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
| 65 |
-
</svg>
|
| 66 |
</button>
|
| 67 |
<button @onclick="() => GoToPage(TotalPages)" disabled="@(CurrentPage >= TotalPages)"
|
| 68 |
-
class="inline-flex h-
|
| 69 |
title="Last page">
|
| 70 |
-
<
|
| 71 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M13 5l7 7-7 7M6 5l7 7-7 7" />
|
| 72 |
-
</svg>
|
| 73 |
</button>
|
| 74 |
</div>
|
| 75 |
</div>
|
|
@@ -84,8 +76,8 @@
|
|
| 84 |
[Parameter] public EventCallback<int> OnPageSizeChanged { get; set; }
|
| 85 |
|
| 86 |
private string GetPageButtonClass(int p) => p == CurrentPage
|
| 87 |
-
? "inline-flex h-
|
| 88 |
-
: "inline-flex h-
|
| 89 |
|
| 90 |
private async Task GoToPage(int p)
|
| 91 |
{
|
|
|
|
| 1 |
@* Partial/Pagination.razor - Reusable pagination component *@
|
| 2 |
|
| 3 |
+
<div class="flex flex-wrap items-center justify-between gap-3 border-t border-slate-100 bg-slate-50/60 px-5 py-3">
|
| 4 |
<div class="text-xs text-slate-500">
|
| 5 |
@if (TotalCount == 0)
|
| 6 |
{
|
|
|
|
| 18 |
<div class="flex items-center gap-2 text-xs text-slate-500">
|
| 19 |
<span>Rows per page</span>
|
| 20 |
<select value="@PageSize" @onchange="HandlePageSizeChange"
|
| 21 |
+
class="rounded-lg border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-600 transition-all">
|
| 22 |
<option value="5">5</option>
|
| 23 |
<option value="10">10</option>
|
| 24 |
<option value="20">20</option>
|
|
|
|
| 28 |
|
| 29 |
<div class="flex items-center gap-1">
|
| 30 |
<button @onclick="() => GoToPage(1)" disabled="@(CurrentPage <= 1)"
|
| 31 |
+
class="inline-flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white hover:text-slate-700 disabled:cursor-not-allowed disabled:opacity-40"
|
| 32 |
title="First page">
|
| 33 |
+
<i data-lucide="chevrons-left" class="h-3.5 w-3.5"></i>
|
|
|
|
|
|
|
| 34 |
</button>
|
| 35 |
<button @onclick="() => GoToPage(CurrentPage - 1)" disabled="@(CurrentPage <= 1)"
|
| 36 |
+
class="inline-flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white hover:text-slate-700 disabled:cursor-not-allowed disabled:opacity-40"
|
| 37 |
title="Previous page">
|
| 38 |
+
<i data-lucide="chevron-left" class="h-3.5 w-3.5"></i>
|
|
|
|
|
|
|
| 39 |
</button>
|
| 40 |
|
| 41 |
@foreach (var p in GetVisiblePages())
|
| 42 |
{
|
| 43 |
if (p == -1)
|
| 44 |
{
|
| 45 |
+
<span class="inline-flex h-8 w-8 items-center justify-center text-xs text-slate-400">...</span>
|
| 46 |
}
|
| 47 |
else
|
| 48 |
{
|
|
|
|
| 54 |
}
|
| 55 |
|
| 56 |
<button @onclick="() => GoToPage(CurrentPage + 1)" disabled="@(CurrentPage >= TotalPages)"
|
| 57 |
+
class="inline-flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white hover:text-slate-700 disabled:cursor-not-allowed disabled:opacity-40"
|
| 58 |
title="Next page">
|
| 59 |
+
<i data-lucide="chevron-right" class="h-3.5 w-3.5"></i>
|
|
|
|
|
|
|
| 60 |
</button>
|
| 61 |
<button @onclick="() => GoToPage(TotalPages)" disabled="@(CurrentPage >= TotalPages)"
|
| 62 |
+
class="inline-flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white hover:text-slate-700 disabled:cursor-not-allowed disabled:opacity-40"
|
| 63 |
title="Last page">
|
| 64 |
+
<i data-lucide="chevrons-right" class="h-3.5 w-3.5"></i>
|
|
|
|
|
|
|
| 65 |
</button>
|
| 66 |
</div>
|
| 67 |
</div>
|
|
|
|
| 76 |
[Parameter] public EventCallback<int> OnPageSizeChanged { get; set; }
|
| 77 |
|
| 78 |
private string GetPageButtonClass(int p) => p == CurrentPage
|
| 79 |
+
? "inline-flex h-8 w-8 items-center justify-center rounded-lg text-xs font-semibold transition-all bg-violet-600 text-white shadow-sm"
|
| 80 |
+
: "inline-flex h-8 w-8 items-center justify-center rounded-lg text-xs font-semibold transition-all text-slate-600 hover:bg-white";
|
| 81 |
|
| 82 |
private async Task GoToPage(int p)
|
| 83 |
{
|
TaskTrackingSystem.WebApp/Components/Partial/SuccessAlert.razor
CHANGED
|
@@ -3,8 +3,8 @@
|
|
| 3 |
|
| 4 |
@if (!string.IsNullOrEmpty(displayMessage))
|
| 5 |
{
|
| 6 |
-
<div class="fixed
|
| 7 |
-
<div class="flex items-center gap-3 rounded-
|
| 8 |
<i data-lucide="check-circle" class="h-5 w-5 shrink-0"></i>
|
| 9 |
<span class="text-sm font-semibold">@displayMessage</span>
|
| 10 |
</div>
|
|
|
|
| 3 |
|
| 4 |
@if (!string.IsNullOrEmpty(displayMessage))
|
| 5 |
{
|
| 6 |
+
<div class="fixed right-4 top-4 z-[100] pointer-events-none sm:right-6 sm:top-6">
|
| 7 |
+
<div class="flex items-center gap-3 rounded-lg border border-teal-500/20 bg-teal-600 px-4 py-3 text-white shadow-lg animate-pulse-once">
|
| 8 |
<i data-lucide="check-circle" class="h-5 w-5 shrink-0"></i>
|
| 9 |
<span class="text-sm font-semibold">@displayMessage</span>
|
| 10 |
</div>
|
TaskTrackingSystem.WebApp/Components/Partial/ThemeToggle.razor
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
|
| 4 |
<button type="button"
|
| 5 |
@onclick="ToggleTheme"
|
| 6 |
-
class="
|
| 7 |
title="@ButtonTitle"
|
| 8 |
aria-label="@ButtonTitle"
|
| 9 |
aria-pressed="@isDarkMode.ToString().ToLowerInvariant()">
|
|
|
|
| 3 |
|
| 4 |
<button type="button"
|
| 5 |
@onclick="ToggleTheme"
|
| 6 |
+
class="control-button"
|
| 7 |
title="@ButtonTitle"
|
| 8 |
aria-label="@ButtonTitle"
|
| 9 |
aria-pressed="@isDarkMode.ToString().ToLowerInvariant()">
|
TaskTrackingSystem.WebApp/wwwroot/app.css
CHANGED
|
@@ -1,29 +1,76 @@
|
|
| 1 |
:root {
|
| 2 |
-
--brand-primary: #
|
| 3 |
-
--brand-
|
| 4 |
-
--
|
| 5 |
-
--
|
| 6 |
-
--
|
| 7 |
-
--
|
| 8 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
html.dark {
|
| 12 |
-
--brand-primary: #
|
| 13 |
-
--brand-
|
| 14 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
--text-primary: #F8FAFC;
|
| 16 |
--text-muted: #CBD5E1;
|
| 17 |
-
--
|
| 18 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
color-scheme: dark;
|
| 20 |
}
|
| 21 |
|
| 22 |
html, body {
|
| 23 |
-
font-family:
|
| 24 |
background-color: var(--app-bg);
|
| 25 |
color: var(--text-primary);
|
| 26 |
scrollbar-gutter: stable;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
* {
|
|
@@ -58,17 +105,17 @@ html, body {
|
|
| 58 |
}
|
| 59 |
|
| 60 |
a, .btn-link {
|
| 61 |
-
color:
|
| 62 |
}
|
| 63 |
|
| 64 |
.btn-primary {
|
| 65 |
color: #fff;
|
| 66 |
-
background-color:
|
| 67 |
-
border-color:
|
| 68 |
}
|
| 69 |
|
| 70 |
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
| 71 |
-
box-shadow: 0 0 0 0.1rem
|
| 72 |
}
|
| 73 |
|
| 74 |
.content {
|
|
@@ -107,7 +154,7 @@ h1:focus {
|
|
| 107 |
|
| 108 |
html.dark a,
|
| 109 |
html.dark .btn-link {
|
| 110 |
-
color: #
|
| 111 |
}
|
| 112 |
|
| 113 |
html.dark,
|
|
@@ -204,7 +251,7 @@ html.dark .border-violet-500,
|
|
| 204 |
html.dark .border-violet-600,
|
| 205 |
html.dark .border-indigo-500,
|
| 206 |
html.dark .border-indigo-600 {
|
| 207 |
-
border-color: #
|
| 208 |
}
|
| 209 |
|
| 210 |
html.dark input,
|
|
@@ -224,7 +271,7 @@ html.dark textarea::placeholder {
|
|
| 224 |
|
| 225 |
html.dark input[type="checkbox"],
|
| 226 |
html.dark input[type="radio"] {
|
| 227 |
-
accent-color: #
|
| 228 |
}
|
| 229 |
|
| 230 |
html.dark .btn-primary,
|
|
@@ -235,7 +282,7 @@ html.dark .bg-purple-500,
|
|
| 235 |
html.dark .bg-purple-600,
|
| 236 |
html.dark .bg-indigo-500,
|
| 237 |
html.dark .bg-indigo-600 {
|
| 238 |
-
background-color: #
|
| 239 |
border-color: #4338CA !important;
|
| 240 |
color: #FFFFFF !important;
|
| 241 |
}
|
|
@@ -250,7 +297,7 @@ html.dark .bg-indigo-700 {
|
|
| 250 |
html.dark .bg-violet-400,
|
| 251 |
html.dark .bg-purple-400,
|
| 252 |
html.dark .bg-indigo-400 {
|
| 253 |
-
background-color: #
|
| 254 |
}
|
| 255 |
|
| 256 |
html.dark .bg-violet-50,
|
|
@@ -271,7 +318,7 @@ html.dark .text-purple-600,
|
|
| 271 |
html.dark .text-purple-700,
|
| 272 |
html.dark .text-indigo-600,
|
| 273 |
html.dark .text-indigo-700 {
|
| 274 |
-
color: #
|
| 275 |
}
|
| 276 |
|
| 277 |
html.dark .bg-rose-50,
|
|
@@ -386,7 +433,7 @@ html.dark .hover\:text-violet-500:hover,
|
|
| 386 |
html.dark .hover\:text-violet-600:hover,
|
| 387 |
html.dark .hover\:text-violet-700:hover,
|
| 388 |
html.dark .hover\:text-violet-800:hover {
|
| 389 |
-
color: #
|
| 390 |
}
|
| 391 |
|
| 392 |
html.dark .hover\:text-amber-600:hover {
|
|
@@ -403,15 +450,15 @@ html.dark .focus\:ring-slate-400:focus,
|
|
| 403 |
html.dark .focus\:ring-slate-900:focus,
|
| 404 |
html.dark .focus\:ring-violet-600:focus,
|
| 405 |
html.dark .focus\:ring-red-500:focus {
|
| 406 |
-
--tw-ring-color: #
|
| 407 |
}
|
| 408 |
|
| 409 |
html.dark .shadow-sm {
|
| 410 |
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35) !important;
|
| 411 |
}
|
| 412 |
|
| 413 |
-
html.dark .shadow-
|
| 414 |
-
html.dark .shadow-
|
| 415 |
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28) !important;
|
| 416 |
}
|
| 417 |
|
|
@@ -438,7 +485,401 @@ html.dark .flatpickr-day:focus {
|
|
| 438 |
html.dark .flatpickr-day.selected,
|
| 439 |
html.dark .flatpickr-day.startRange,
|
| 440 |
html.dark .flatpickr-day.endRange {
|
| 441 |
-
background:
|
| 442 |
-
border-color:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
color: #FFFFFF;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
}
|
|
|
|
| 1 |
:root {
|
| 2 |
+
--brand-primary: #2563EB;
|
| 3 |
+
--brand-primary-hover: #1D4ED8;
|
| 4 |
+
--brand-accent: #0F766E;
|
| 5 |
+
--app-bg: #F6F8FB;
|
| 6 |
+
--surface: #FFFFFF;
|
| 7 |
+
--surface-muted: #F8FAFC;
|
| 8 |
+
--surface-raised: #FFFFFF;
|
| 9 |
+
--border: #E2E8F0;
|
| 10 |
+
--border-strong: #CBD5E1;
|
| 11 |
+
--text-primary: #0F172A;
|
| 12 |
+
--text-muted: #64748B;
|
| 13 |
+
--text-soft: #94A3B8;
|
| 14 |
+
--focus-ring: rgba(37, 99, 235, 0.24);
|
| 15 |
+
--status-success-bg: #CCFBF1;
|
| 16 |
+
--status-success-text: #0F766E;
|
| 17 |
+
--status-warning-bg: #FEF3C7;
|
| 18 |
+
--status-warning-text: #B45309;
|
| 19 |
+
--status-danger-bg: #FFE4E6;
|
| 20 |
+
--status-danger-text: #BE123C;
|
| 21 |
+
--sidebar-bg: #0B1220;
|
| 22 |
+
--sidebar-muted: #9AA6B8;
|
| 23 |
+
--sidebar-active: rgba(37, 99, 235, 0.18);
|
| 24 |
+
--sidebar-border: rgba(148, 163, 184, 0.16);
|
| 25 |
+
--radius-sm: 0.375rem;
|
| 26 |
+
--radius-md: 0.5rem;
|
| 27 |
+
--radius-lg: 0.5rem;
|
| 28 |
+
--shadow-card: 0 1px 2px rgba(15, 23, 42, 0.06);
|
| 29 |
+
--shadow-popover: 0 18px 40px rgba(15, 23, 42, 0.16);
|
| 30 |
}
|
| 31 |
|
| 32 |
html.dark {
|
| 33 |
+
--brand-primary: #60A5FA;
|
| 34 |
+
--brand-primary-hover: #93C5FD;
|
| 35 |
+
--brand-accent: #2DD4BF;
|
| 36 |
+
--app-bg: #0B1120;
|
| 37 |
+
--surface: #111827;
|
| 38 |
+
--surface-muted: #182235;
|
| 39 |
+
--surface-raised: #0F172A;
|
| 40 |
+
--border: #263449;
|
| 41 |
+
--border-strong: #334155;
|
| 42 |
--text-primary: #F8FAFC;
|
| 43 |
--text-muted: #CBD5E1;
|
| 44 |
+
--text-soft: #94A3B8;
|
| 45 |
+
--focus-ring: rgba(96, 165, 250, 0.30);
|
| 46 |
+
--status-success-bg: rgba(20, 184, 166, 0.18);
|
| 47 |
+
--status-success-text: #5EEAD4;
|
| 48 |
+
--status-warning-bg: rgba(245, 158, 11, 0.18);
|
| 49 |
+
--status-warning-text: #FCD34D;
|
| 50 |
+
--status-danger-bg: rgba(244, 63, 94, 0.18);
|
| 51 |
+
--status-danger-text: #FDA4AF;
|
| 52 |
+
--sidebar-bg: #060B16;
|
| 53 |
+
--sidebar-muted: #94A3B8;
|
| 54 |
+
--sidebar-active: rgba(96, 165, 250, 0.18);
|
| 55 |
+
--sidebar-border: rgba(148, 163, 184, 0.18);
|
| 56 |
+
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.28);
|
| 57 |
+
--shadow-popover: 0 18px 50px rgba(0, 0, 0, 0.35);
|
| 58 |
color-scheme: dark;
|
| 59 |
}
|
| 60 |
|
| 61 |
html, body {
|
| 62 |
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 63 |
background-color: var(--app-bg);
|
| 64 |
color: var(--text-primary);
|
| 65 |
scrollbar-gutter: stable;
|
| 66 |
+
font-size: 15px;
|
| 67 |
+
letter-spacing: 0;
|
| 68 |
+
-webkit-font-smoothing: antialiased;
|
| 69 |
+
text-rendering: optimizeLegibility;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
body.tts-sidebar-open {
|
| 73 |
+
overflow: hidden;
|
| 74 |
}
|
| 75 |
|
| 76 |
* {
|
|
|
|
| 105 |
}
|
| 106 |
|
| 107 |
a, .btn-link {
|
| 108 |
+
color: var(--brand-primary);
|
| 109 |
}
|
| 110 |
|
| 111 |
.btn-primary {
|
| 112 |
color: #fff;
|
| 113 |
+
background-color: var(--brand-primary);
|
| 114 |
+
border-color: var(--brand-primary);
|
| 115 |
}
|
| 116 |
|
| 117 |
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
| 118 |
+
box-shadow: 0 0 0 0.1rem var(--surface), 0 0 0 0.25rem var(--focus-ring);
|
| 119 |
}
|
| 120 |
|
| 121 |
.content {
|
|
|
|
| 154 |
|
| 155 |
html.dark a,
|
| 156 |
html.dark .btn-link {
|
| 157 |
+
color: #93C5FD;
|
| 158 |
}
|
| 159 |
|
| 160 |
html.dark,
|
|
|
|
| 251 |
html.dark .border-violet-600,
|
| 252 |
html.dark .border-indigo-500,
|
| 253 |
html.dark .border-indigo-600 {
|
| 254 |
+
border-color: #93C5FD !important;
|
| 255 |
}
|
| 256 |
|
| 257 |
html.dark input,
|
|
|
|
| 271 |
|
| 272 |
html.dark input[type="checkbox"],
|
| 273 |
html.dark input[type="radio"] {
|
| 274 |
+
accent-color: #2563EB;
|
| 275 |
}
|
| 276 |
|
| 277 |
html.dark .btn-primary,
|
|
|
|
| 282 |
html.dark .bg-purple-600,
|
| 283 |
html.dark .bg-indigo-500,
|
| 284 |
html.dark .bg-indigo-600 {
|
| 285 |
+
background-color: #2563EB !important;
|
| 286 |
border-color: #4338CA !important;
|
| 287 |
color: #FFFFFF !important;
|
| 288 |
}
|
|
|
|
| 297 |
html.dark .bg-violet-400,
|
| 298 |
html.dark .bg-purple-400,
|
| 299 |
html.dark .bg-indigo-400 {
|
| 300 |
+
background-color: #93C5FD !important;
|
| 301 |
}
|
| 302 |
|
| 303 |
html.dark .bg-violet-50,
|
|
|
|
| 318 |
html.dark .text-purple-700,
|
| 319 |
html.dark .text-indigo-600,
|
| 320 |
html.dark .text-indigo-700 {
|
| 321 |
+
color: #93C5FD !important;
|
| 322 |
}
|
| 323 |
|
| 324 |
html.dark .bg-rose-50,
|
|
|
|
| 433 |
html.dark .hover\:text-violet-600:hover,
|
| 434 |
html.dark .hover\:text-violet-700:hover,
|
| 435 |
html.dark .hover\:text-violet-800:hover {
|
| 436 |
+
color: #93C5FD !important;
|
| 437 |
}
|
| 438 |
|
| 439 |
html.dark .hover\:text-amber-600:hover {
|
|
|
|
| 450 |
html.dark .focus\:ring-slate-900:focus,
|
| 451 |
html.dark .focus\:ring-violet-600:focus,
|
| 452 |
html.dark .focus\:ring-red-500:focus {
|
| 453 |
+
--tw-ring-color: #93C5FD !important;
|
| 454 |
}
|
| 455 |
|
| 456 |
html.dark .shadow-sm {
|
| 457 |
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35) !important;
|
| 458 |
}
|
| 459 |
|
| 460 |
+
html.dark .shadow-lg,
|
| 461 |
+
html.dark .shadow-lg {
|
| 462 |
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28) !important;
|
| 463 |
}
|
| 464 |
|
|
|
|
| 485 |
html.dark .flatpickr-day.selected,
|
| 486 |
html.dark .flatpickr-day.startRange,
|
| 487 |
html.dark .flatpickr-day.endRange {
|
| 488 |
+
background: var(--brand-primary);
|
| 489 |
+
border-color: var(--brand-primary);
|
| 490 |
+
color: #FFFFFF;
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
/* Taskify UI system */
|
| 494 |
+
.app-shell {
|
| 495 |
+
display: flex;
|
| 496 |
+
min-height: 100vh;
|
| 497 |
+
background:
|
| 498 |
+
linear-gradient(180deg, rgba(37, 99, 235, 0.035), transparent 18rem),
|
| 499 |
+
var(--app-bg);
|
| 500 |
+
color: var(--text-primary);
|
| 501 |
+
overflow: hidden;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.app-sidebar {
|
| 505 |
+
position: relative;
|
| 506 |
+
display: flex;
|
| 507 |
+
width: 17rem;
|
| 508 |
+
min-height: 100vh;
|
| 509 |
+
flex-shrink: 0;
|
| 510 |
+
flex-direction: column;
|
| 511 |
+
overflow: hidden;
|
| 512 |
+
border-right: 1px solid var(--sidebar-border);
|
| 513 |
+
background: var(--sidebar-bg);
|
| 514 |
+
color: #FFFFFF;
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
.app-sidebar::before {
|
| 518 |
+
position: absolute;
|
| 519 |
+
inset: 0;
|
| 520 |
+
pointer-events: none;
|
| 521 |
+
content: "";
|
| 522 |
+
background:
|
| 523 |
+
radial-gradient(circle at top left, rgba(37, 99, 235, 0.22), transparent 18rem),
|
| 524 |
+
linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 42%);
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.app-sidebar > * {
|
| 528 |
+
position: relative;
|
| 529 |
+
z-index: 1;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.app-sidebar-brand {
|
| 533 |
+
display: flex;
|
| 534 |
+
min-height: 4.75rem;
|
| 535 |
+
align-items: center;
|
| 536 |
+
gap: 0.75rem;
|
| 537 |
+
padding: 1rem 1.25rem;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.app-sidebar-logo {
|
| 541 |
+
display: inline-flex;
|
| 542 |
+
height: 2.4rem;
|
| 543 |
+
width: 2.4rem;
|
| 544 |
+
align-items: center;
|
| 545 |
+
justify-content: center;
|
| 546 |
+
border-radius: var(--radius-lg);
|
| 547 |
+
background: linear-gradient(135deg, var(--brand-primary), var(--brand-accent));
|
| 548 |
color: #FFFFFF;
|
| 549 |
+
box-shadow: 0 10px 28px rgba(37, 99, 235, 0.28);
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
.app-sidebar-title {
|
| 553 |
+
display: flex;
|
| 554 |
+
flex-direction: column;
|
| 555 |
+
line-height: 1.15;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.app-sidebar-title strong {
|
| 559 |
+
font-size: 1.1rem;
|
| 560 |
+
letter-spacing: 0;
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
.app-sidebar-title span {
|
| 564 |
+
margin-top: 0.15rem;
|
| 565 |
+
color: var(--sidebar-muted);
|
| 566 |
+
font-size: 0.72rem;
|
| 567 |
+
font-weight: 600;
|
| 568 |
+
letter-spacing: 0.08em;
|
| 569 |
+
text-transform: uppercase;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.app-sidebar-user {
|
| 573 |
+
flex-shrink: 0;
|
| 574 |
+
border-top: 1px solid var(--sidebar-border);
|
| 575 |
+
padding: 0.875rem;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.app-main {
|
| 579 |
+
display: flex;
|
| 580 |
+
min-width: 0;
|
| 581 |
+
flex: 1;
|
| 582 |
+
flex-direction: column;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
.app-topbar {
|
| 586 |
+
display: flex;
|
| 587 |
+
min-height: 4rem;
|
| 588 |
+
align-items: center;
|
| 589 |
+
justify-content: space-between;
|
| 590 |
+
gap: 1rem;
|
| 591 |
+
border-bottom: 1px solid var(--border);
|
| 592 |
+
background: color-mix(in srgb, var(--surface) 92%, transparent);
|
| 593 |
+
padding: 0 1.5rem;
|
| 594 |
+
backdrop-filter: blur(12px);
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
.app-topbar-title {
|
| 598 |
+
min-width: 0;
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.app-topbar-title h1 {
|
| 602 |
+
margin: 0;
|
| 603 |
+
color: var(--text-primary);
|
| 604 |
+
font-size: 1.05rem;
|
| 605 |
+
font-weight: 750;
|
| 606 |
+
letter-spacing: 0;
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.app-topbar-title p {
|
| 610 |
+
margin: 0.1rem 0 0;
|
| 611 |
+
color: var(--text-muted);
|
| 612 |
+
font-size: 0.78rem;
|
| 613 |
+
font-weight: 500;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
.app-content {
|
| 617 |
+
flex: 1;
|
| 618 |
+
overflow-y: auto;
|
| 619 |
+
padding: clamp(1rem, 2vw, 2rem);
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
.mobile-sidebar-toggle,
|
| 623 |
+
.mobile-sidebar-close {
|
| 624 |
+
display: none;
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
.sidebar-scrim {
|
| 628 |
+
display: none;
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
.control-button {
|
| 632 |
+
display: inline-flex;
|
| 633 |
+
height: 2.35rem;
|
| 634 |
+
min-width: 2.35rem;
|
| 635 |
+
align-items: center;
|
| 636 |
+
justify-content: center;
|
| 637 |
+
border: 1px solid var(--border);
|
| 638 |
+
border-radius: var(--radius-lg);
|
| 639 |
+
background: var(--surface);
|
| 640 |
+
color: var(--text-muted);
|
| 641 |
+
box-shadow: var(--shadow-card);
|
| 642 |
+
transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
.control-button:hover {
|
| 646 |
+
border-color: var(--border-strong);
|
| 647 |
+
background: var(--surface-muted);
|
| 648 |
+
color: var(--brand-primary);
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
.control-button:focus-visible {
|
| 652 |
+
outline: none;
|
| 653 |
+
box-shadow: 0 0 0 4px var(--focus-ring);
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
.surface-panel {
|
| 657 |
+
border: 1px solid var(--border);
|
| 658 |
+
border-radius: var(--radius-lg);
|
| 659 |
+
background: var(--surface);
|
| 660 |
+
box-shadow: var(--shadow-card);
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
.page-kicker {
|
| 664 |
+
color: var(--brand-primary);
|
| 665 |
+
font-size: 0.74rem;
|
| 666 |
+
font-weight: 750;
|
| 667 |
+
letter-spacing: 0.08em;
|
| 668 |
+
text-transform: uppercase;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
.text-balance {
|
| 672 |
+
text-wrap: balance;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
main :is(input, select, textarea, .form-control, .form-select) {
|
| 676 |
+
border-color: var(--border) !important;
|
| 677 |
+
border-radius: var(--radius-lg) !important;
|
| 678 |
+
background-color: var(--surface) !important;
|
| 679 |
+
color: var(--text-primary) !important;
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
main :is(input, select, textarea, .form-control, .form-select):focus {
|
| 683 |
+
border-color: var(--brand-primary) !important;
|
| 684 |
+
outline: none !important;
|
| 685 |
+
box-shadow: 0 0 0 4px var(--focus-ring) !important;
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
main :is(input, textarea)::placeholder {
|
| 689 |
+
color: var(--text-soft) !important;
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
main table {
|
| 693 |
+
width: 100%;
|
| 694 |
+
border-collapse: separate;
|
| 695 |
+
border-spacing: 0;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
main thead tr {
|
| 699 |
+
background: var(--surface-muted) !important;
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
main th {
|
| 703 |
+
color: var(--text-muted) !important;
|
| 704 |
+
white-space: nowrap;
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
main td {
|
| 708 |
+
vertical-align: middle;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
main tbody tr {
|
| 712 |
+
transition: background-color 0.14s ease;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
main tbody tr:hover {
|
| 716 |
+
background: var(--surface-muted) !important;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
main .shadow-\[0_18px_50px_rgba\(15\,23\,42\,0\.08\)\],
|
| 720 |
+
main .shadow-\[0_18px_50px_rgba\(15\,23\,42\,0\.12\)\] {
|
| 721 |
+
box-shadow: var(--shadow-card) !important;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
main .rounded-\[32px\],
|
| 725 |
+
main .rounded-\[28px\],
|
| 726 |
+
main .rounded-\[24px\] {
|
| 727 |
+
border-radius: var(--radius-lg) !important;
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
.flatpickr-calendar {
|
| 731 |
+
border-color: var(--border);
|
| 732 |
+
border-radius: var(--radius-lg);
|
| 733 |
+
box-shadow: var(--shadow-popover);
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
html.dark a,
|
| 737 |
+
html.dark .btn-link,
|
| 738 |
+
html.dark .text-violet-500,
|
| 739 |
+
html.dark .text-violet-600,
|
| 740 |
+
html.dark .text-violet-700,
|
| 741 |
+
html.dark .text-purple-600,
|
| 742 |
+
html.dark .text-purple-700,
|
| 743 |
+
html.dark .text-indigo-600,
|
| 744 |
+
html.dark .text-indigo-700 {
|
| 745 |
+
color: var(--brand-primary) !important;
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
html.dark .bg-white,
|
| 749 |
+
html.dark .bg-card,
|
| 750 |
+
html.dark .bg-popover {
|
| 751 |
+
background-color: var(--surface) !important;
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
html.dark .bg-slate-50,
|
| 755 |
+
html.dark .bg-slate-50\/40,
|
| 756 |
+
html.dark .bg-slate-50\/50,
|
| 757 |
+
html.dark .bg-slate-50\/60,
|
| 758 |
+
html.dark .bg-slate-50\/70,
|
| 759 |
+
html.dark .bg-muted {
|
| 760 |
+
background-color: var(--surface-muted) !important;
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
html.dark .border-slate-100,
|
| 764 |
+
html.dark .border-slate-200,
|
| 765 |
+
html.dark .border-slate-300,
|
| 766 |
+
html.dark .border-border,
|
| 767 |
+
html.dark .divide-slate-100,
|
| 768 |
+
html.dark .divide-slate-200 {
|
| 769 |
+
border-color: var(--border) !important;
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
html.dark .text-slate-950,
|
| 773 |
+
html.dark .text-slate-900,
|
| 774 |
+
html.dark .text-slate-800,
|
| 775 |
+
html.dark .text-slate-700,
|
| 776 |
+
html.dark .text-foreground,
|
| 777 |
+
html.dark .text-card-foreground,
|
| 778 |
+
html.dark .text-popover-foreground {
|
| 779 |
+
color: var(--text-primary) !important;
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
html.dark .text-slate-600,
|
| 783 |
+
html.dark .text-slate-500,
|
| 784 |
+
html.dark .text-muted,
|
| 785 |
+
html.dark .text-muted-foreground {
|
| 786 |
+
color: var(--text-muted) !important;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
html.dark .text-slate-400,
|
| 790 |
+
html.dark .text-slate-300 {
|
| 791 |
+
color: var(--text-soft) !important;
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
html.dark .btn-primary,
|
| 795 |
+
html.dark .bg-primary,
|
| 796 |
+
html.dark .bg-violet-500,
|
| 797 |
+
html.dark .bg-violet-600,
|
| 798 |
+
html.dark .bg-violet-700,
|
| 799 |
+
html.dark .bg-purple-500,
|
| 800 |
+
html.dark .bg-purple-600,
|
| 801 |
+
html.dark .bg-indigo-500,
|
| 802 |
+
html.dark .bg-indigo-600 {
|
| 803 |
+
background-color: var(--brand-primary) !important;
|
| 804 |
+
border-color: var(--brand-primary) !important;
|
| 805 |
+
color: #FFFFFF !important;
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
html.dark .hover\:bg-violet-600:hover,
|
| 809 |
+
html.dark .hover\:bg-violet-700:hover,
|
| 810 |
+
html.dark .hover\:bg-purple-700:hover,
|
| 811 |
+
html.dark .hover\:bg-indigo-100:hover {
|
| 812 |
+
background-color: var(--brand-primary-hover) !important;
|
| 813 |
+
color: #FFFFFF !important;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
@media (max-width: 1023px) {
|
| 817 |
+
.app-shell {
|
| 818 |
+
display: block;
|
| 819 |
+
min-height: 100vh;
|
| 820 |
+
overflow: auto;
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
.app-sidebar {
|
| 824 |
+
position: fixed;
|
| 825 |
+
inset: 0 auto 0 0;
|
| 826 |
+
z-index: 60;
|
| 827 |
+
width: min(18rem, calc(100vw - 3rem));
|
| 828 |
+
transform: translateX(-100%);
|
| 829 |
+
transition: transform 0.22s ease;
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
body.tts-sidebar-open .app-sidebar {
|
| 833 |
+
transform: translateX(0);
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
.sidebar-scrim {
|
| 837 |
+
position: fixed;
|
| 838 |
+
inset: 0;
|
| 839 |
+
z-index: 50;
|
| 840 |
+
display: none;
|
| 841 |
+
border: 0;
|
| 842 |
+
background: rgba(15, 23, 42, 0.48);
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
body.tts-sidebar-open .sidebar-scrim {
|
| 846 |
+
display: block;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
.mobile-sidebar-toggle,
|
| 850 |
+
.mobile-sidebar-close {
|
| 851 |
+
display: inline-flex;
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
.app-main {
|
| 855 |
+
min-height: 100vh;
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
.app-topbar {
|
| 859 |
+
min-height: 3.75rem;
|
| 860 |
+
padding: 0 1rem;
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
.app-content {
|
| 864 |
+
padding: 1rem;
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
.app-topbar-title p {
|
| 868 |
+
display: none;
|
| 869 |
+
}
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
@media (max-width: 640px) {
|
| 873 |
+
html,
|
| 874 |
+
body {
|
| 875 |
+
font-size: 14px;
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
.app-content {
|
| 879 |
+
padding: 0.875rem;
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
main table {
|
| 883 |
+
min-width: 760px;
|
| 884 |
+
}
|
| 885 |
}
|