@page "/tasks" @page "/tasks/all" @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Microsoft.AspNetCore.Authorization.Authorize] @inject ApiClientService ApiClient @inject IJSRuntime JS @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization @inject NavigationManager Navigation Tasks

Tasks

@(isAdmin ? "View and manage all tasks across projects." : "Your assigned tasks.")

@if (canCreateTask) { }
@if (isLoading) { } else {
@if (PagedTasks.Any()) { @foreach (var entry in PagedTasks.Select((task, index) => (task, index))) { var task = entry.task; } } else { }
Task Code Task Status Priority Due Date Project Assignee Actions
@GetTaskCode(task.Id) @task.Title @if (!string.IsNullOrEmpty(task.Description)) {

@task.Description

}
@GetStatusLabel(task.StatusId) @GetPriorityLabel(task.PriorityId) @DisplayFormats.Date(task.DueDate) @{ var proj = projects.FirstOrDefault(p => p.Id == task.ProjectId); } @(proj?.Name ?? $"#{task.ProjectId}") @{ var user = allUsers.FirstOrDefault(u => u.Id == task.AssignedTo); } @if (user != null) {
@(user.FirstName.Length > 0 ? user.FirstName[0].ToString().ToUpper() : "")@(user.LastName.Length > 0 ? user.LastName[0].ToString().ToUpper() : "") @user.FirstName @user.LastName
} else { Unassigned }
@if (canEditTask) { } @if (canDeleteTask) { }
}
@* ===== Create / Edit Modal ===== *@ @if (showModal) {

@(isEditing ? "Edit Task" : "New Task")

@if (isTitleError) { }
@if (isTitleError) {

@titleErrorMessage

}
@if (isProjectError) {

@projectErrorMessage

}
@if (isAssigneeDropdownOpen) {
@if (isLoadingProjectMembers) {
Loading project members...
} else {
@foreach (var user in FilteredProjectMembers) { var isSelected = formAssignedTo == user.Id; } @if (!FilteredProjectMembers.Any()) {
No members match the current search.
}
}
}
@if (!string.IsNullOrEmpty(errorMessage)) {

@errorMessage

}
} @* ===== Confirm Dialog ===== *@ @code { [Parameter] [SupplyParameterFromQuery(Name = "taskId")] public long? TaskIdParam { get; set; } private bool isLoading = true; private bool isAdmin = false; private bool isManager = false; private bool canCreateTask = false; private bool canEditTask = false; private bool canDeleteTask = false; private bool canUpdateTask = false; private long currentUserId = 0; private bool showModal = false; private bool isEditing = false; private bool isSaving = false; private bool isAssigneeDropdownOpen = false; private string? errorMessage; // Per-field validation private bool isTitleError = false; private string titleErrorMessage = ""; private bool isProjectError = false; private string projectErrorMessage = ""; // Confirm dialog state private bool showConfirmDialog = false; private string confirmTitle = ""; private string confirmMessage = ""; private string confirmButtonText = ""; private string confirmIcon = ""; private string confirmIconBg = ""; private string confirmIconText = ""; private string confirmButtonClass = ""; private Func? confirmAction; private List taskList = new(); private List projects = new(); private List allUsers = new(); private List projectMembers = new(); private string? successMessage; // Search and Filters state private string searchInput = ""; private long filterProjectIdInput = 0; private AppTaskStatus? filterStatusIdInput = null; private TaskPriority? filterPriorityIdInput = null; // Pagination state private int currentPage = 1; private int pageSize = 10; private int totalCount = 0; private int totalPages = 0; private int TotalPages => totalPages; private int TotalCount => totalCount; private IEnumerable PagedTasks => taskList; // Form fields private long editingId; private string formTitle = ""; private string? formDescription; private long formProjectId; private AppTaskStatus formStatusId = AppTaskStatus.Todo; private TaskPriority formPriorityId = TaskPriority.Medium; private DateTime formDueDate = DateTime.Today.AddDays(7); private long? formAssignedTo; private string assigneeSearchInput = ""; private bool isLoadingProjectMembers; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; isAdmin = user.IsInRole("Admin"); isManager = user.IsInRole("Manager"); var idStr = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (idStr != null) long.TryParse(idStr, out currentUserId); await LoadTaskAccessAsync(user); var client = ApiClient.CreateClient(); try { var tasksTask = LoadTasksPageAsync(client); var projectsTask = client.GetFromJsonAsync>("Project", Serialization.CaseInsensitive); var usersTask = client.GetFromJsonAsync>("User", Serialization.CaseInsensitive); await System.Threading.Tasks.Task.WhenAll(tasksTask, projectsTask, usersTask); taskList = tasksTask.Result?.Items ?? new(); totalCount = tasksTask.Result?.TotalCount ?? 0; totalPages = tasksTask.Result?.TotalPages ?? 0; currentPage = tasksTask.Result?.Page ?? currentPage; pageSize = tasksTask.Result?.PageSize ?? pageSize; projects = projectsTask.Result ?? new(); allUsers = usersTask.Result ?? new(); } catch (Exception ex) { Console.WriteLine($"Load error: {ex.Message}"); } finally { if (TaskIdParam.HasValue && TaskIdParam > 0) { await FocusTaskAsync(TaskIdParam.Value); } } isLoading = false; } protected override async Task OnAfterRenderAsync(bool firstRender) { await JS.InvokeVoidAsync("initIcons"); } private async Task?> LoadTasksPageAsync(HttpClient? client = null) { client ??= ApiClient.CreateClient(); var response = await client.GetFromJsonAsync>(BuildTasksUrl(), Serialization.CaseInsensitive); if (response != null) { return response; } return null; } // Pagination handlers private async Task HandlePageChanged(int page) { currentPage = page; await LoadTasks(); } private async Task HandlePageSizeChanged(int size) { pageSize = size; currentPage = 1; await LoadTasks(); } private async Task ApplySearch() { currentPage = 1; await LoadTasks(); } private async Task ResetSearch() { searchInput = ""; filterProjectIdInput = 0; filterStatusIdInput = null; filterPriorityIdInput = null; currentPage = 1; await LoadTasks(); } private async Task LoadTasks() { var client = ApiClient.CreateClient(); var result = await LoadTasksPageAsync(client); taskList = result?.Items ?? new(); totalCount = result?.TotalCount ?? 0; totalPages = result?.TotalPages ?? 0; currentPage = result?.Page ?? currentPage; pageSize = result?.PageSize ?? pageSize; } private string BuildTasksUrl() { var query = new List { $"page={currentPage}", $"limit={pageSize}" }; if (!string.IsNullOrWhiteSpace(searchInput)) { query.Add($"search={Uri.EscapeDataString(searchInput.Trim())}"); } if (filterProjectIdInput > 0) { query.Add($"projectId={filterProjectIdInput}"); } if (filterStatusIdInput.HasValue) { query.Add($"statusId={(int)filterStatusIdInput.Value}"); } if (filterPriorityIdInput.HasValue) { query.Add($"priorityId={(int)filterPriorityIdInput.Value}"); } if (!(isAdmin || isManager) && currentUserId > 0) { query.Add("assignedOnly=true"); } return $"Task?{string.Join("&", query)}"; } private async Task FocusTaskAsync(long taskId) { try { var client = ApiClient.CreateClient(); var task = await client.GetFromJsonAsync($"Task/{taskId}", Serialization.CaseInsensitive); if (task == null) { return; } searchInput = task.Title; currentPage = 1; await LoadTasks(); } catch (Exception ex) { Console.WriteLine($"Focus task error: {ex.Message}"); } } private async Task LoadTaskAccessAsync(System.Security.Claims.ClaimsPrincipal user) { canCreateTask = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Tasks_Create"); canEditTask = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Tasks_Update"); canDeleteTask = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Tasks_Delete"); canUpdateTask = canEditTask; } private IEnumerable FilteredProjectMembers { get { var result = projectMembers.Where(u => u.IsActive); if (!string.IsNullOrWhiteSpace(assigneeSearchInput)) { var search = assigneeSearchInput.Trim(); result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(search, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrWhiteSpace(u.Username) && u.Username.Contains(search, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrWhiteSpace(u.Email) && u.Email.Contains(search, StringComparison.OrdinalIgnoreCase))); } return result .OrderBy(u => u.FirstName) .ThenBy(u => u.LastName) .ToList(); } } private string SelectedAssigneeLabel { get { if (!formAssignedTo.HasValue) { return "Unassigned"; } var selectedUser = projectMembers.FirstOrDefault(u => u.Id == formAssignedTo.Value); return selectedUser == null ? "Selected member" : $"{selectedUser.FirstName} {selectedUser.LastName}".Trim(); } } private async Task OnFormProjectChanged(ChangeEventArgs e) { if (long.TryParse(e.Value?.ToString(), out var projectId)) { formProjectId = projectId; } else { formProjectId = 0; } formAssignedTo = null; assigneeSearchInput = ""; await LoadProjectMembersAsync(formProjectId); } private void SetFormAssignee(long? userId) { formAssignedTo = userId; errorMessage = null; isAssigneeDropdownOpen = false; } private void OpenAssigneeDropdown() { if (formProjectId <= 0) { return; } isAssigneeDropdownOpen = true; } private void ToggleAssigneeDropdown() { if (isAssigneeDropdownOpen) { CloseAssigneeDropdown(); return; } OpenAssigneeDropdown(); } private void EnsureAssigneeDropdownOpen() { if (formProjectId <= 0) { return; } isAssigneeDropdownOpen = true; } private void CloseAssigneeDropdown() { isAssigneeDropdownOpen = false; } private async Task LoadProjectMembersAsync(long projectId) { projectMembers.Clear(); isLoadingProjectMembers = true; try { if (projectId <= 0) { return; } var client = ApiClient.CreateClient(); var membersResult = await client.GetFromJsonAsync>>($"Project/{projectId}/members", Serialization.CaseInsensitive); if (membersResult?.IsSuccess == true && membersResult.Value != null) { projectMembers = membersResult.Value .Where(user => user.IsActive) .OrderBy(user => user.FirstName) .ThenBy(user => user.LastName) .ToList(); if (formAssignedTo.HasValue && !projectMembers.Any(user => user.Id == formAssignedTo.Value)) { formAssignedTo = null; } } else { formAssignedTo = null; } } catch (Exception ex) { Console.WriteLine($"Failed to load project members for task form: {ex.Message}"); formAssignedTo = null; } finally { isLoadingProjectMembers = false; await InvokeAsync(StateHasChanged); await JS.InvokeVoidAsync("initIcons"); } } private async Task OpenCreateModal() { isEditing = false; editingId = 0; formTitle = ""; formDescription = null; formProjectId = projects.FirstOrDefault()?.Id ?? 0; formStatusId = AppTaskStatus.Todo; formPriorityId = TaskPriority.Medium; formDueDate = DateTime.Today.AddDays(7); formAssignedTo = null; assigneeSearchInput = ""; errorMessage = null; isTitleError = false; titleErrorMessage = ""; isProjectError = false; projectErrorMessage = ""; showModal = true; await LoadProjectMembersAsync(formProjectId); } private async Task OpenEditModal(TaskDto task) { isEditing = true; editingId = task.Id; formTitle = task.Title; formDescription = task.Description; formProjectId = task.ProjectId; formStatusId = task.StatusId; formPriorityId = task.PriorityId; formDueDate = task.DueDate; formAssignedTo = task.AssignedTo; assigneeSearchInput = ""; errorMessage = null; isTitleError = false; titleErrorMessage = ""; isProjectError = false; projectErrorMessage = ""; showModal = true; await LoadProjectMembersAsync(formProjectId); } private void CloseModal() { showModal = false; errorMessage = null; isTitleError = false; titleErrorMessage = ""; isProjectError = false; projectErrorMessage = ""; assigneeSearchInput = ""; isAssigneeDropdownOpen = false; projectMembers.Clear(); isLoadingProjectMembers = false; } private void RequestSaveConfirmation() { isTitleError = false; titleErrorMessage = ""; isProjectError = false; projectErrorMessage = ""; bool hasError = false; if (string.IsNullOrWhiteSpace(formTitle)) { isTitleError = true; titleErrorMessage = "Task title is required."; hasError = true; } if (formProjectId == 0) { isProjectError = true; projectErrorMessage = "Please select a project."; hasError = true; } if (hasError) return; if (isEditing) { confirmTitle = "Update Task?"; confirmMessage = "Are you sure you want to update this task?"; confirmButtonText = "Update"; confirmIcon = "pencil"; confirmIconBg = "bg-amber-50"; confirmIconText = "text-amber-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } else { confirmTitle = "Create Task?"; confirmMessage = "Are you sure you want to create this task?"; confirmButtonText = "Create"; confirmIcon = "plus-circle"; confirmIconBg = "bg-emerald-50"; confirmIconText = "text-emerald-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } confirmAction = SaveTask; showConfirmDialog = true; } private void RequestDeleteConfirmation(long id) { confirmTitle = "Delete Task?"; confirmMessage = "Are you sure you want to delete this task? This action cannot be undone."; confirmButtonText = "Delete"; confirmIcon = "trash-2"; confirmIconBg = "bg-rose-50"; confirmIconText = "text-rose-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; confirmAction = () => DeleteTask(id); showConfirmDialog = true; } private async Task HandleConfirmAction() { showConfirmDialog = false; if (confirmAction != null) await confirmAction(); } private void CloseConfirmDialog() { showConfirmDialog = false; } private async Task SaveTask() { if (string.IsNullOrWhiteSpace(formTitle)) { errorMessage = ResultMessages.TaskTitleRequired; return; } if (formProjectId == 0) { errorMessage = ResultMessages.SelectProjectRequired; return; } isSaving = true; errorMessage = null; var client = ApiClient.CreateClient(); try { if (isEditing) { var dto = new UpdateTaskDto { Title = formTitle, Description = formDescription, StatusId = formStatusId, PriorityId = formPriorityId, DueDate = formDueDate, AssignedTo = formAssignedTo }; var response = await client.PutAsJsonAsync($"Task/{editingId}", dto); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToUpdateTask; return; } } else { var dto = new CreateTaskDto { Title = formTitle, Description = formDescription, ProjectId = formProjectId, StatusId = formStatusId, PriorityId = formPriorityId, DueDate = formDueDate, AssignedTo = formAssignedTo }; var response = await client.PostAsJsonAsync("Task", dto); var res = await response.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToCreateTask; return; } } CloseModal(); await LoadTasks(); successMessage = isEditing ? "Task updated successfully!" : "Task created successfully!"; } catch (Exception ex) { errorMessage = ex.Message; } finally { isSaving = false; } } private async Task DeleteTask(long id) { var client = ApiClient.CreateClient(); try { var response = await client.DeleteAsync($"Task/{id}"); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { await JS.InvokeVoidAsync("alert", res?.ErrorMessage ?? "Failed to delete task."); return; } await LoadTasks(); if (currentPage > TotalPages && TotalPages > 0) currentPage = TotalPages; successMessage = "Task deleted successfully!"; } catch (Exception ex) { Console.WriteLine($"Delete error: {ex.Message}"); } } private string GetStatusBadge(AppTaskStatus statusId) => statusId switch { AppTaskStatus.Todo => "bg-slate-100 text-slate-700", AppTaskStatus.InProgress => "bg-blue-100 text-blue-700", AppTaskStatus.Done => "bg-emerald-100 text-emerald-700", _ => "bg-slate-100 text-slate-600" }; private string GetStatusLabel(AppTaskStatus statusId) => statusId switch { AppTaskStatus.Todo => "To Do", AppTaskStatus.InProgress => "In Progress", AppTaskStatus.Done => "Done", _ => "Unknown" }; private static string GetTaskCode(long taskId) => $"TSK-{taskId:D4}"; private string GetPriorityBadge(TaskPriority priorityId) => priorityId switch { TaskPriority.Low => "bg-slate-100 text-slate-600", TaskPriority.Medium => "bg-amber-100 text-amber-700", TaskPriority.High => "bg-red-100 text-red-700", _ => "bg-slate-100 text-slate-600" }; private string GetPriorityLabel(TaskPriority priorityId) => priorityId switch { TaskPriority.Low => "Low", TaskPriority.Medium => "Medium", TaskPriority.High => "High", _ => "-" }; }