@page "/tasks/{TaskId:long}/details" @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using TaskTrackingSystem.Shared @using TaskTrackingSystem.Shared.Models.Task @using TaskTrackingSystem.Shared.Models.Comment @using TaskTrackingSystem.Shared.Models.Project @using TaskTrackingSystem.Shared.Models.User @using TaskTrackingSystem.WebApp.Components.Partial @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Authorize] @inject ApiClientService ApiClient @inject IJSRuntime JS @inject NavigationManager Navigation @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization Task Details
@if (ViewMode != "Embedded") {
Workspace Tasks Task Details
Back to Tasks
} @if (isLoading) { } else if (task == null) {

Task Not Found

The task you are looking for does not exist or has been deleted.

} else {
TASK-#@task.Id @GetStatusLabel(task.StatusId) @GetPriorityLabel(task.PriorityId) @if (project != null) { @project.Name }
@if (isEditingTitle) {
} else {

@task.Title @if (hasTaskManagePermission) { }

}

Description

@if (hasTaskManagePermission && !isEditingDescription) { }
@if (isEditingDescription) {
} else {
@(string.IsNullOrWhiteSpace(task.Description) ? "No description provided for this task." : task.Description)
}

Properties

Status
Priority @if (hasTaskManagePermission) { } else {
@GetPriorityLabel(task.PriorityId)
}
Assignee @if (hasTaskManagePermission) { } else {
@if (assignee != null) { @(assignee.FirstName.Length > 0 ? assignee.FirstName[0].ToString().ToUpper() : "") @assignee.FirstName @assignee.LastName } else { Unassigned }
}
Due Date @if (hasTaskManagePermission) { } else {
@DisplayFormats.Date(task.DueDate)
}
Created At
@task.CreatedAt.ToString("dd-MM-yyyy HH:mm")

Comments (@comments.Count)

@if (comments.Any()) { @foreach (var c in comments) {
@(c.UserFullName.Length > 0 ? c.UserFullName[0].ToString().ToUpper() : "U")
@c.UserFullName @c.CreatedAt.ToString("g")

@c.UserRoleName

@c.Message
@if (c.UserId == currentUserId || isAdmin) { }
} } else {

No comments posted yet.

Start the conversation below!

}
@if (!string.IsNullOrEmpty(commentError)) {

@commentError

}
}
@code { [Parameter] public long TaskId { get; set; } [Parameter] public string? ViewMode { get; set; } [Parameter] public EventCallback OnTaskUpdated { get; set; } [SupplyParameterFromQuery(Name = "tab")] public string? TabQuery { get; set; } private Func? confirmAction; private bool isLoading = true; private TaskDto? task; private ProjectDto? project; private UserDto? assignee; private List comments = new(); private List allUsers = new(); private bool isAdmin = false; private bool canEditTask = false; private bool hasTaskManagePermission = false; private long currentUserId = 0; private string userRoleName = "Employee"; private List scopes = new(); // Comments Form private string newCommentMessage = ""; private bool isSavingComment = false; private string? commentError; // Confirm dialog private bool showConfirm = false; private string confirmTitle = ""; private string confirmMessage = ""; // Inline edit states private bool isEditingDescription = false; private bool isSavingDescription = false; private string tempDescription = ""; private bool isEditingTitle = false; private bool isSavingTitle = false; private string tempTitle = ""; private long lastLoadedTaskId = 0; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; isAdmin = user.IsInRole("Admin"); // Task details editing is driven by role permissions so future roles can be granted access. canEditTask = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Tasks_Update"); hasTaskManagePermission = canEditTask; var roleClaim = user.FindFirst(System.Security.Claims.ClaimTypes.Role)?.Value; userRoleName = roleClaim ?? "Employee"; var idStr = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (idStr != null) long.TryParse(idStr, out currentUserId); } protected override async Task OnParametersSetAsync() { if (TaskId > 0 && TaskId != lastLoadedTaskId) { lastLoadedTaskId = TaskId; assignee = null; await LoadAllData(); } } protected override async Task OnAfterRenderAsync(bool firstRender) { await JS.InvokeVoidAsync("initIcons"); } private async Task LoadAllData() { isLoading = true; var client = ApiClient.CreateClient(); try { var taskUrl = $"Task/{TaskId}"; var commentsUrl = $"TaskDetails/tasks/{TaskId}/comments"; var usersUrl = "User"; var taskResponseTask = client.GetAsync(taskUrl); var commentsResponseTask = client.GetAsync(commentsUrl); var usersResponseTask = client.GetAsync(usersUrl); await Task.WhenAll(taskResponseTask, commentsResponseTask, usersResponseTask); var taskResponse = taskResponseTask.Result; if (taskResponse.IsSuccessStatusCode) { task = await taskResponse.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); } var commentsResponse = commentsResponseTask.Result; if (commentsResponse.IsSuccessStatusCode) { comments = await commentsResponse.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive) ?? new(); } var usersResponse = usersResponseTask.Result; if (usersResponse.IsSuccessStatusCode) { allUsers = await usersResponse.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive) ?? new(); } if (task != null) { // Fetch project details var projResponse = await client.GetAsync($"Project/{task.ProjectId}"); if (projResponse.IsSuccessStatusCode) { project = await projResponse.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); } // Set assignee details if (task.AssignedTo.HasValue) { assignee = allUsers.FirstOrDefault(u => u.Id == task.AssignedTo.Value); } CalculateScopes(); } } catch (Exception ex) { Console.WriteLine($"Load task details error: {ex.Message}"); } finally { isLoading = false; } } private void CalculateScopes() { scopes = new List(); if (task == null) return; if (hasTaskManagePermission) { scopes.Add("task:all"); scopes.Add("task:write"); scopes.Add("comment:write"); } else { scopes.Add("task:read"); // Comments stay available to everyone who can view the task. scopes.Add("comment:write"); } } // ─── INLINE MUTATIONS ────────────────────────────────────────────────────── private async Task OnStatusChanged(ChangeEventArgs e) { if (task == null) return; if (Enum.TryParse(e.Value?.ToString(), out var newStatus)) { var client = ApiClient.CreateClient(); try { var response = await client.PatchAsync($"Task/{task.Id}/status?statusId={(int)newStatus}", null); if (response.IsSuccessStatusCode) { task.StatusId = newStatus; await OnTaskUpdated.InvokeAsync(); await JS.InvokeVoidAsync("initIcons"); } } catch (Exception ex) { Console.WriteLine($"Update status error: {ex.Message}"); } } } private async Task OnPriorityChanged(ChangeEventArgs e) { if (Enum.TryParse(e.Value?.ToString(), out var newPriority)) { await UpdateTaskProperty(dto => dto.PriorityId = newPriority); } } private async Task OnAssigneeChanged(ChangeEventArgs e) { if (long.TryParse(e.Value?.ToString(), out var assigneeId)) { var newAssigneeId = assigneeId > 0 ? (long?)assigneeId : null; await UpdateTaskProperty(dto => dto.AssignedTo = newAssigneeId); assignee = newAssigneeId.HasValue ? allUsers.FirstOrDefault(u => u.Id == newAssigneeId.Value) : null; } } private async Task OnDueDateChanged(ChangeEventArgs e) { if (DateTime.TryParse(e.Value?.ToString(), out var newDueDate)) { await UpdateTaskProperty(dto => dto.DueDate = newDueDate); } } private async Task UpdateTaskProperty(Action applyChange) { if (task == null) return; var dto = new UpdateTaskDto { Title = task.Title, Description = task.Description, StatusId = task.StatusId, PriorityId = task.PriorityId, DueDate = task.DueDate, AssignedTo = task.AssignedTo, AssignedBy = task.AssignedBy }; applyChange(dto); var client = ApiClient.CreateClient(); try { var response = await client.PutAsJsonAsync($"Task/{task.Id}", dto); if (response.IsSuccessStatusCode) { task.Title = dto.Title; task.Description = dto.Description; task.StatusId = dto.StatusId; task.PriorityId = dto.PriorityId; task.DueDate = dto.DueDate; task.AssignedTo = dto.AssignedTo; await OnTaskUpdated.InvokeAsync(); await JS.InvokeVoidAsync("initIcons"); } } catch (Exception ex) { Console.WriteLine($"Update task property error: {ex.Message}"); } } private void StartEditDescription() { if (task == null) return; tempDescription = task.Description ?? ""; isEditingDescription = true; } private void CancelEditDescription() { isEditingDescription = false; } private async Task SaveDescription() { isSavingDescription = true; await UpdateTaskProperty(dto => dto.Description = tempDescription); isEditingDescription = false; isSavingDescription = false; } private void StartEditTitle() { if (task == null) return; tempTitle = task.Title; isEditingTitle = true; } private void CancelEditTitle() { isEditingTitle = false; } private async Task SaveTitle() { if (string.IsNullOrWhiteSpace(tempTitle)) return; isSavingTitle = true; await UpdateTaskProperty(dto => dto.Title = tempTitle.Trim()); isEditingTitle = false; isSavingTitle = false; } // ─── COMMENTS ACTIONS ────────────────────────────────────────────────────── private async Task PostComment() { if (string.IsNullOrWhiteSpace(newCommentMessage)) return; isSavingComment = true; commentError = null; try { var client = ApiClient.CreateClient(); var response = await client.PostAsJsonAsync($"TaskDetails/tasks/{TaskId}/comments", new CreateCommentDto { Message = newCommentMessage }); var res = await response.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive); if (response.IsSuccessStatusCode && res != null && res.IsSuccess && res.Value != null) { comments.Add(res.Value); newCommentMessage = ""; await JS.InvokeVoidAsync("initIcons"); } else { commentError = res?.ErrorMessage ?? "Failed to post comment."; } } catch (Exception ex) { commentError = ex.Message; } finally { isSavingComment = false; } } private void ConfirmDeleteComment(long commentId) { confirmTitle = "Delete Comment?"; confirmMessage = "Are you sure you want to delete this comment? This action cannot be undone."; confirmAction = () => DeleteComment(commentId); showConfirm = true; } private async Task DeleteComment(long commentId) { var client = ApiClient.CreateClient(); try { var response = await client.DeleteAsync($"TaskDetails/comments/{commentId}"); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (response.IsSuccessStatusCode && res != null && res.IsSuccess) { var idx = comments.FindIndex(c => c.Id == commentId); if (idx >= 0) comments.RemoveAt(idx); } } catch (Exception ex) { Console.WriteLine($"Delete comment error: {ex.Message}"); } } // --- CONFIRM DIALOG LOGIC ------------------------------------------------- private async Task ExecuteConfirmAction() { showConfirm = false; if (confirmAction != null) { await confirmAction(); } confirmAction = null; } private void CancelConfirm() { showConfirm = false; confirmAction = null; } // --- DISPLAY UTILITIES ---------------------------------------------------- private string GetStatusLabel(AppTaskStatus statusId) => statusId switch { AppTaskStatus.Todo => "To Do", AppTaskStatus.InProgress => "In Progress", AppTaskStatus.Done => "Done", _ => "Unknown" }; private string GetStatusBadge(AppTaskStatus statusId) => statusId switch { AppTaskStatus.Todo => "bg-slate-100 text-slate-800 border border-slate-200", AppTaskStatus.InProgress => "bg-blue-50 text-blue-700 border border-blue-100", AppTaskStatus.Done => "bg-emerald-50 text-emerald-700 border border-emerald-100", _ => "bg-slate-50 text-slate-600" }; private string GetPriorityLabel(TaskPriority priorityId) => priorityId switch { TaskPriority.Low => "Low", TaskPriority.Medium => "Medium", TaskPriority.High => "High", _ => "Unknown" }; private string GetPriorityBadge(TaskPriority priorityId) => priorityId switch { TaskPriority.Low => "bg-slate-100 text-slate-600", TaskPriority.Medium => "bg-amber-50 text-amber-700 border border-amber-100", TaskPriority.High => "bg-rose-50 text-rose-700 border border-rose-100", _ => "bg-slate-50 text-slate-600" }; }