@page "/issues" @page "/issues/all" @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using TaskTrackingSystem.Shared @using TaskTrackingSystem.Shared.Enums @using TaskTrackingSystem.Shared.Models.Issue @using TaskTrackingSystem.Shared.Models.Project @using TaskTrackingSystem.Shared.Models.Task @using TaskTrackingSystem.Shared.Models.User @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Authorize] @inject ApiClientService ApiClient @inject IJSRuntime JS @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization @inject NavigationManager Navigation @AppLocalization.PageTitle("issues", "Issues")

@AppLocalization.PageTitle("issues", "Issues")

@AppLocalization.Text("page.issues.desc", "Track daily work items, update progress, and review what was done.")

@if (canCreateIssue) { }
@if (isLoading) { } else {
@if (issues.Any()) { @foreach (var entry in issues.Select((issue, index) => (issue, index))) { var issue = entry.issue; } } else { }
@AppLocalization.Text("common.taskCode", "Task Code") @AppLocalization.Text("common.issue", "Issue") @AppLocalization.Text("common.hours", "Hours") @AppLocalization.Text("common.status", "Status") @AppLocalization.Text("common.priority", "Priority") @AppLocalization.Text("common.date", "Date") @AppLocalization.Text("common.assignee", "Assignee") @AppLocalization.Text("common.risk", "Risk") @AppLocalization.Text("common.actions", "Actions")
@GetTaskCode(issue.TaskId) @(issue.ActualHours?.ToString("0.##") ?? "-") @AppLocalization.Text("common.hoursAbbrev", "hr") @GetStatusLabel(issue.StatusId) @GetPriorityLabel(issue.PriorityId) @AppLocalization.Text("common.due", "Due"): @issue.DueDate.ToString("MMM d, yyyy") @(!string.IsNullOrWhiteSpace(issue.AssignedToName) ? issue.AssignedToName : AppLocalization.Text("common.unassigned", "Unassigned")) @if (issue.IsBlocked) { @AppLocalization.Text("status.blocked", "Blocked") } else if (issue.EscalationLevel > 0) { @GetEscalationLabel(issue.EscalationLevel) } else { @AppLocalization.Text("status.clear", "Clear") }
@if (canEditIssue) { } @if (canDeleteIssue) { }
}
@if (showModal) {

@AppLocalization.Text("common.editIssue", "Edit Issue")

#@editingId

@AppLocalization.Text("common.task", "Task")

@SelectedTaskLabel

@AppLocalization.Text("common.project", "Project")

@SelectedProjectLabel

@if (!string.IsNullOrWhiteSpace(errorMessage)) {

@errorMessage

}
} @code { private bool isLoading = true; private bool isSaving; private bool canCreateIssue; private bool canEditIssue; private bool canDeleteIssue; private bool isAdmin; private bool showModal; private bool showDeleteConfirm; private bool showSaveConfirm; private string? successMessage; private string? errorMessage; private string deleteConfirmMessage = "Are you sure you want to delete this issue?"; private long deleteIssueId; private long currentUserId; private List issues = new(); private List tasks = new(); private List projects = new(); private List projectMembers = new(); private int currentPage = 1; private int pageSize = 10; private int totalCount; private int totalPages; private string searchInput = ""; private long taskIdInput; private long projectIdInput; private AppTaskStatus? statusIdInput; private TaskPriority? priorityIdInput; private bool assignedOnly; private long editingId; private long formTaskId; private string formTitle = ""; private string? formDescription; private long? formAssignedTo; private decimal? formActualHours = 7m; private DateTime formStartDate = DateTime.Today; private DateTime formDueDate = DateTime.Today; private AppTaskStatus formStatusId = AppTaskStatus.Todo; private TaskPriority formPriorityId = TaskPriority.Medium; private string? formDelayReason; private bool formIsBlocked; private string? formBlockedBy; private int formEscalationLevel; private string SelectedTaskLabel => tasks.FirstOrDefault(t => t.Id == formTaskId)?.Title ?? $"Task #{formTaskId}"; private string SelectedProjectLabel => tasks.FirstOrDefault(t => t.Id == formTaskId) is { } task ? projects.FirstOrDefault(p => p.Id == task.ProjectId)?.Name ?? $"Project #{task.ProjectId}" : "Unknown project"; private IEnumerable filteredProjectMembers => projectMembers.Where(u => u.IsActive); private static string GetTaskCode(long taskId) => $"TSK-{taskId:D4}"; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; isAdmin = user.IsInRole("Admin"); if (long.TryParse(user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var userId)) { currentUserId = userId; } canCreateIssue = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Issues_Create"); canEditIssue = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Issues_Update"); canDeleteIssue = isAdmin || await MenuAuthorization.HasAccessCodeAsync(user, "Issues_Delete"); var client = ApiClient.CreateClient(); try { var issuesTask = LoadIssuesPageAsync(client); var tasksTask = client.GetFromJsonAsync>("Task", Serialization.CaseInsensitive); var projectsTask = client.GetFromJsonAsync>("Project", Serialization.CaseInsensitive); var usersTask = client.GetFromJsonAsync>("User", Serialization.CaseInsensitive); await System.Threading.Tasks.Task.WhenAll(issuesTask, tasksTask, projectsTask, usersTask); ApplyPageResult(issuesTask.Result); tasks = tasksTask.Result ?? new(); projects = projectsTask.Result ?? new(); projectMembers = usersTask.Result?.Where(u => u.IsActive).OrderBy(u => u.FirstName).ThenBy(u => u.LastName).ToList() ?? new(); } catch (Exception ex) { errorMessage = ex.Message; } finally { isLoading = false; } } protected override async Task OnAfterRenderAsync(bool firstRender) { await JS.InvokeVoidAsync("initIcons"); } private async Task?> LoadIssuesPageAsync(HttpClient? client = null) { client ??= ApiClient.CreateClient(); return await client.GetFromJsonAsync>(BuildIssuesUrl(), Serialization.CaseInsensitive); } private void ApplyPageResult(PagedResult? result) { issues = result?.Items ?? new(); totalCount = result?.TotalCount ?? 0; totalPages = result?.TotalPages ?? 0; currentPage = result?.Page ?? currentPage; pageSize = result?.PageSize ?? pageSize; } private async Task LoadIssues() { var client = ApiClient.CreateClient(); var result = await LoadIssuesPageAsync(client); ApplyPageResult(result); } private string BuildIssuesUrl() { var query = new List { $"page={currentPage}", $"limit={pageSize}" }; if (!string.IsNullOrWhiteSpace(searchInput)) { query.Add($"search={Uri.EscapeDataString(searchInput.Trim())}"); } if (taskIdInput > 0) { query.Add($"taskId={taskIdInput}"); } if (projectIdInput > 0) { query.Add($"projectId={projectIdInput}"); } if (statusIdInput.HasValue) { query.Add($"statusId={(int)statusIdInput.Value}"); } if (priorityIdInput.HasValue) { query.Add($"priorityId={(int)priorityIdInput.Value}"); } if (assignedOnly) { query.Add("assignedOnly=true"); } return $"Issue?{string.Join("&", query)}"; } private async Task ApplySearch() { currentPage = 1; await LoadIssues(); } private async Task ResetSearch() { searchInput = ""; taskIdInput = 0; projectIdInput = 0; statusIdInput = null; priorityIdInput = null; assignedOnly = false; currentPage = 1; await LoadIssues(); } private async Task HandlePageChanged(int page) { currentPage = page; await LoadIssues(); } private async Task HandlePageSizeChanged(int size) { pageSize = size; currentPage = 1; await LoadIssues(); } private void GoToAddIssue() { Navigation.NavigateTo("/issues/add"); } private async Task OpenEditModal(IssueDto issue) { if (!canEditIssue) { return; } editingId = issue.Id; formTaskId = issue.TaskId; formTitle = issue.Title; formDescription = issue.Description; formAssignedTo = issue.AssignedTo; formActualHours = issue.ActualHours; formStartDate = issue.StartDate; formDueDate = issue.DueDate; formStatusId = issue.StatusId; formPriorityId = issue.PriorityId; formDelayReason = issue.DelayReason; formIsBlocked = issue.IsBlocked; formBlockedBy = issue.BlockedBy; formEscalationLevel = issue.EscalationLevel; errorMessage = null; showModal = true; await LoadMembersForTaskAsync(issue.TaskId); } private void CloseModal() { showModal = false; errorMessage = null; editingId = 0; } private async Task LoadMembersForTaskAsync(long taskId) { projectMembers.Clear(); var task = tasks.FirstOrDefault(t => t.Id == taskId); if (task == null) { return; } var client = ApiClient.CreateClient(); try { var membersResult = await client.GetFromJsonAsync>>($"Project/{task.ProjectId}/members", Serialization.CaseInsensitive); if (membersResult?.IsSuccess == true && membersResult.Value != null) { projectMembers = membersResult.Value.Where(u => u.IsActive).OrderBy(u => u.FirstName).ThenBy(u => u.LastName).ToList(); } } catch (Exception ex) { Console.WriteLine($"Failed to load issue assignees: {ex.Message}"); } } private void RequestSaveConfirmation() { showSaveConfirm = true; } private void CloseSaveConfirm() { showSaveConfirm = false; } private async Task ConfirmSave() { showSaveConfirm = false; await SaveIssueAsync(); } private async Task SaveIssueAsync() { if (editingId <= 0) { return; } isSaving = true; errorMessage = null; try { var client = ApiClient.CreateClient(); var dto = new UpdateIssueDto { Title = formTitle.Trim(), Description = formDescription, AssignedTo = formAssignedTo, ActualHours = formActualHours, DelayReason = formDelayReason, IsBlocked = formIsBlocked, BlockedBy = formBlockedBy, EscalationLevel = formEscalationLevel, StartDate = formStartDate, DueDate = formDueDate, StatusId = formStatusId, PriorityId = formPriorityId }; var response = await client.PutAsJsonAsync($"Issue/{editingId}", dto); if (response.IsSuccessStatusCode) { successMessage = "Issue updated successfully!"; showModal = false; await LoadIssues(); } else { errorMessage = await ReadErrorMessageAsync(response); } } catch (Exception ex) { errorMessage = ex.Message; } finally { isSaving = false; } } private void RequestDeleteConfirmation(long issueId) { if (!canDeleteIssue) { return; } deleteIssueId = issueId; deleteConfirmMessage = "Are you sure you want to delete this issue?"; showDeleteConfirm = true; } private void CloseDeleteConfirm() { showDeleteConfirm = false; } private async Task ConfirmDelete() { showDeleteConfirm = false; try { var client = ApiClient.CreateClient(); var response = await client.DeleteAsync($"Issue/{deleteIssueId}"); if (response.IsSuccessStatusCode) { successMessage = "Issue deleted successfully!"; await LoadIssues(); } else { errorMessage = await ReadErrorMessageAsync(response); } } catch (Exception ex) { errorMessage = ex.Message; } } private static async Task ReadErrorMessageAsync(HttpResponseMessage response) { var body = await response.Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(body)) { return $"Request failed with status {(int)response.StatusCode}."; } try { using var doc = System.Text.Json.JsonDocument.Parse(body); if (doc.RootElement.TryGetProperty("message", out var message)) { return message.GetString() ?? body; } } catch { } return body; } 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 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", _ => "Unknown" }; private static string GetEscalationLabel(int level) => level switch { 1 => "Team Lead", 2 => "PM", 3 => "Manager", _ => "None" }; }