Spaces:
Running
Running
task-tracking-system / TaskTrackingSystem.WebApp /Components /Pages /Features /Tasks /TaskAssign.razor
| @page "/tasks/assign" | |
| @using Microsoft.AspNetCore.Authorization | |
| @using Microsoft.AspNetCore.Components.Authorization | |
| @using TaskTrackingSystem.Shared.Models.Task | |
| @using TaskTrackingSystem.Shared.Models.Project | |
| @using TaskTrackingSystem.Shared.Models.Role | |
| @rendermode @(new InteractiveServerRenderMode(prerender: false)) | |
| @attribute [Microsoft.AspNetCore.Authorization.Authorize] | |
| @inject ApiClientService ApiClient | |
| @inject IJSRuntime JS | |
| @inject NavigationManager Navigation | |
| @inject AuthenticationStateProvider AuthStateProvider | |
| @inject MenuAuthorizationService MenuAuthorization | |
| <PageTitle>@AppLocalization.PageTitle("taskAssignment", "Task Assignment")</PageTitle> | |
| <SuccessAlert Message="@successMessage" /> | |
| <div class="space-y-6"> | |
| <!-- Page Header --> | |
| <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> | |
| <div> | |
| <h2 class="text-3xl font-bold tracking-tight text-slate-900">@AppLocalization.PageTitle("taskAssignment", "Task Assignment")</h2> | |
| <p class="text-slate-500 mt-1">Assign tasks to project members and track task owners.</p> | |
| </div> | |
| </div> | |
| @if (isLoading) | |
| { | |
| <LoadingSpinner Message="@AppLocalization.Text("common.loadingTasksAndUsers", "Loading tasks and users...")" /> | |
| } | |
| else | |
| { | |
| <!-- Project Filter Bar --> | |
| <div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm"> | |
| <div class="flex items-center gap-4"> | |
| <label class="text-sm font-semibold text-slate-700">Filter by Project:</label> | |
| <div class="w-64"> | |
| <select @bind="selectedProjectIdInput" | |
| class="w-full rounded-lg border border-slate-200 px-3 py-1.5 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-violet-600 focus:border-transparent transition-all"> | |
| <option value="0">All Projects</option> | |
| @foreach (var p in projects) | |
| { | |
| <option value="@p.Id">@p.Name</option> | |
| } | |
| </select> | |
| </div> | |
| <button @onclick="ApplyProjectFilter" class="inline-flex items-center rounded-lg bg-violet-600 px-4 py-1.5 text-xs font-medium text-white hover:bg-violet-700 transition-colors shadow-sm"> | |
| <i data-lucide="search" class="h-3.5 w-3.5 mr-1.5"></i> Search | |
| </button> | |
| <button @onclick="ResetProjectFilter" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50 transition-colors shadow-sm"> | |
| <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Left Panel: Tasks List --> | |
| <div class="lg:col-span-1 rounded-xl border border-slate-200 bg-white shadow-sm flex flex-col h-[550px] overflow-hidden"> | |
| <div class="p-4 border-b border-slate-100 bg-slate-50/60"> | |
| <h3 class="font-bold text-slate-800 text-sm mb-3">Tasks</h3> | |
| <div class="relative flex gap-2"> | |
| <div class="relative flex-1"> | |
| <input @bind="taskSearchInput" type="text" placeholder="@AppLocalization.Text("common.searchTasks", "Search tasks...")" | |
| class="w-full rounded-lg border border-slate-200 pl-9 pr-3 py-1.5 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-600 focus:border-transparent transition-all" /> | |
| <i data-lucide="search" class="absolute left-3 top-2.5 h-3.5 w-3.5 text-slate-400"></i> | |
| </div> | |
| <button @onclick="ApplyTaskSearch" class="inline-flex items-center rounded-lg bg-violet-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-violet-700 transition-colors shrink-0"> | |
| <i data-lucide="search" class="h-3.5 w-3.5 mr-1.5"></i> Search | |
| </button> | |
| <button @onclick="ResetTaskSearch" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50 transition-colors shrink-0"> | |
| <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex-1 overflow-y-auto divide-y divide-slate-100"> | |
| @if (FilteredTasks.Any()) | |
| { | |
| @foreach (var task in FilteredTasks) | |
| { | |
| var isSelected = SelectedTask?.Id == task.Id; | |
| var proj = projects.FirstOrDefault(p => p.Id == task.ProjectId); | |
| <button @onclick="() => SelectTask(task)" | |
| class="w-full text-left p-4 hover:bg-slate-50/60 transition-colors flex flex-col gap-1.5 @(isSelected ? "bg-violet-50/60 border-l-4 border-violet-600 pl-3" : "")"> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-xs font-mono font-bold text-slate-400">TSK-@task.Id</span> | |
| <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[9px] font-semibold @GetStatusBadge(task.StatusId)"> | |
| @GetStatusLabel(task.StatusId) | |
| </span> | |
| </div> | |
| <span class="text-sm font-semibold text-slate-900 @(isSelected ? "text-violet-700" : "")">@task.Title</span> | |
| <div class="flex items-center justify-between text-[10px] text-slate-500"> | |
| <span>@(proj?.Name ?? AppLocalization.Text("common.noProject", "No Project"))</span> | |
| <span class="flex items-center"> | |
| <i data-lucide="user" class="h-3 w-3 mr-1"></i> | |
| @{ var assignee = allUsers.FirstOrDefault(u => u.Id == task.AssignedTo); } | |
| @(assignee != null ? $"{assignee.FirstName} {assignee.LastName}" : AppLocalization.Text("common.unassigned", "Unassigned")) | |
| </span> | |
| </div> | |
| </button> | |
| } | |
| } | |
| else | |
| { | |
| <div class="p-8 text-center text-slate-400 text-xs">@AppLocalization.Text("common.noTasksFound", "No tasks found.")</div> | |
| } | |
| </div> | |
| </div> | |
| <!-- Right Panel: Assignee Picker --> | |
| <div class="lg:col-span-2 rounded-xl border border-slate-200 bg-white shadow-sm flex flex-col h-[550px] overflow-hidden"> | |
| @if (SelectedTask == null) | |
| { | |
| <div class="flex-1 flex flex-col items-center justify-center p-8 text-center bg-slate-50/30"> | |
| <div class="h-12 w-12 rounded-full bg-slate-100 flex items-center justify-center text-slate-400 mb-3"> | |
| <i data-lucide="check-square" class="h-6 w-6"></i> | |
| </div> | |
| <h4 class="text-sm font-bold text-slate-800">No Task Selected</h4> | |
| <p class="text-xs text-slate-500 mt-1 max-w-sm">@AppLocalization.Text("task.selectTaskPrompt", "Select a task from the left panel to assign it to a team member.")</p> | |
| </div> | |
| } | |
| else | |
| { | |
| <!-- Selected Task Details --> | |
| <div class="p-6 border-b border-slate-100 bg-slate-50"> | |
| <div class="flex items-center gap-2 mb-2"> | |
| <span class="text-[10px] font-bold text-violet-600 uppercase tracking-wider">@AppLocalization.Text("common.currentlyAssigningTask", "Currently Assigning Task")</span> | |
| <span class="text-[10px] font-mono font-semibold text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded">TSK-@SelectedTask.Id</span> | |
| </div> | |
| <h3 class="text-xl font-bold text-slate-900">@SelectedTask.Title</h3> | |
| @if (!string.IsNullOrEmpty(SelectedTask.Description)) | |
| { | |
| <p class="text-xs text-slate-500 mt-1 line-clamp-2">@SelectedTask.Description</p> | |
| } | |
| <div class="flex flex-wrap items-center gap-3 mt-4"> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold @GetStatusBadge(SelectedTask.StatusId)"> | |
| Status: @GetStatusLabel(SelectedTask.StatusId) | |
| </span> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold @GetPriorityBadge(SelectedTask.PriorityId)"> | |
| Priority: @GetPriorityLabel(SelectedTask.PriorityId) | |
| </span> | |
| <span class="text-xs text-slate-500"> | |
| <i data-lucide="calendar" class="h-3 w-3 inline mr-1 text-slate-400"></i> | |
| Due: @DisplayFormats.Date(SelectedTask.DueDate) | |
| </span> | |
| </div> | |
| </div> | |
| <!-- Filter & User Selection --> | |
| <div class="px-6 py-3 border-b border-slate-100 bg-slate-50/30 flex flex-col sm:flex-row sm:items-center justify-between gap-3"> | |
| <div class="relative max-w-xs flex-1 flex gap-2"> | |
| <div class="relative flex-1"> | |
| <input @bind="userSearchInput" type="text" placeholder="@AppLocalization.Text("common.searchTeamMembers", "Search team members...")" | |
| class="w-full rounded-lg border border-slate-200 pl-9 pr-3 py-1.5 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-600 focus:border-transparent transition-all" /> | |
| <i data-lucide="search" class="absolute left-3 top-2.5 h-3.5 w-3.5 text-slate-400"></i> | |
| </div> | |
| <button @onclick="ApplyUserSearch" class="inline-flex items-center rounded-lg bg-violet-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-violet-700 transition-colors shrink-0"> | |
| <i data-lucide="search" class="h-3.5 w-3.5 mr-1.5"></i> Search | |
| </button> | |
| <button @onclick="ResetUserSearch" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50 transition-colors shrink-0"> | |
| <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset | |
| </button> | |
| </div> | |
| @if (isAdminUser) | |
| { | |
| <div class="w-40 shrink-0"> | |
| <select @bind="selectedRoleId" class="w-full rounded-lg border border-slate-200 px-3 py-1.5 text-xs text-slate-900 focus:outline-none focus:ring-2 focus:ring-violet-600 focus:border-transparent transition-all"> | |
| <option value="0">@AppLocalization.Text("common.allRoles", "All Roles")</option> | |
| @foreach (var r in roles) | |
| { | |
| <option value="@r.Id">@r.Name</option> | |
| } | |
| </select> | |
| </div> | |
| } | |
| else | |
| { | |
| <div class="text-xs font-semibold text-violet-700 bg-violet-50 border border-violet-100 px-3 py-1.5 rounded-lg shrink-0"> | |
| Employees in this project | |
| </div> | |
| } | |
| <div class="text-xs font-semibold text-slate-600 shrink-0"> | |
| @AppLocalization.Text("common.selected", "Selected"): <span class="text-violet-700 font-bold">@(TargetAssigneeId.HasValue ? allUsers.FirstOrDefault(u => u.Id == TargetAssigneeId)?.Username ?? AppLocalization.Text("common.selected", "Selected") : AppLocalization.Text("common.unassigned", "Unassigned"))</span> | |
| </div> | |
| </div> | |
| <!-- Members picker --> | |
| <div class="flex-1 overflow-y-auto p-6"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> | |
| <!-- Unassigned option --> | |
| <label class="flex items-center gap-3 p-3 rounded-xl border border-dashed border-slate-200 bg-white hover:bg-slate-50/50 cursor-pointer transition-all select-none @(!TargetAssigneeId.HasValue ? "border-violet-600 bg-violet-50/10" : "")"> | |
| <input type="radio" name="assigneeRadio" checked="@(!TargetAssigneeId.HasValue)" | |
| @onchange="() => SetTargetAssignee(null)" | |
| class="h-4 w-4 text-violet-600 focus:ring-violet-500" /> | |
| <span class="flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-slate-500 text-xs font-bold shrink-0"> | |
| <i data-lucide="user-minus" class="h-4 w-4"></i> | |
| </span> | |
| <div class="min-w-0 flex-1"> | |
| <p class="text-sm font-semibold text-slate-800">@AppLocalization.Text("common.unassigned", "Unassigned")</p> | |
| <p class="text-xs text-slate-400">@AppLocalization.Text("common.removeOwnerFromTask", "Remove owner from this task")</p> | |
| </div> | |
| </label> | |
| @foreach (var user in FilteredUsers) | |
| { | |
| var isSelected = TargetAssigneeId == user.Id; | |
| <label class="flex items-center gap-3 p-3 rounded-xl border border-slate-100 bg-white hover:bg-slate-50/50 hover:border-slate-200 cursor-pointer transition-all shadow-sm select-none @(isSelected ? "border-violet-600 bg-violet-50/10" : "")"> | |
| <input type="radio" name="assigneeRadio" checked="@isSelected" | |
| @onchange="() => SetTargetAssignee(user.Id)" | |
| class="h-4 w-4 text-violet-600 focus:ring-violet-500" /> | |
| <span class="flex h-9 w-9 items-center justify-center rounded-full bg-violet-100 text-violet-700 text-xs font-bold shrink-0"> | |
| @(user.FirstName.Length > 0 ? user.FirstName[0].ToString().ToUpper() : "")@(user.LastName.Length > 0 ? user.LastName[0].ToString().ToUpper() : "") | |
| </span> | |
| <div class="min-w-0 flex-1"> | |
| <p class="text-sm font-semibold text-slate-800 truncate">@user.FirstName @user.LastName</p> | |
| <p class="text-xs text-slate-400 truncate">@@@user.Username</p> | |
| </div> | |
| </label> | |
| } | |
| </div> | |
| </div> | |
| <!-- Action bar --> | |
| <div class="p-6 border-t border-slate-100 bg-slate-50/50 flex flex-col sm:flex-row sm:items-center justify-between gap-4"> | |
| <div> | |
| @if (!string.IsNullOrEmpty(statusMessage)) | |
| { | |
| <p class="text-sm font-medium @(isError ? "text-red-600" : "text-emerald-700")"> | |
| <i data-lucide="@(isError ? "alert-circle" : "check-circle")" class="h-4 w-4 inline mr-1.5"></i> | |
| @statusMessage | |
| </p> | |
| } | |
| </div> | |
| @if (canSaveAssignments) | |
| { | |
| <button @onclick="RequestSaveConfirmation" disabled="@isSaving" | |
| class="w-full sm:w-auto inline-flex items-center justify-center rounded-lg bg-violet-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-violet-700 disabled:opacity-50 transition-colors shadow-sm"> | |
| <i data-lucide="user-check" class="h-4 w-4 mr-2"></i> | |
| @(isSaving ? AppLocalization.Text("common.loadingSpinner", "Loading...") : AppLocalization.Text("common.saveAssignment", "Save Assignment")) | |
| </button> | |
| } | |
| </div> | |
| } | |
| </div> | |
| </div> | |
| } | |
| </div> | |
| <ConfirmDialog IsVisible="showConfirmDialog" | |
| Title="@AppLocalization.Text("task.saveAssignmentTitle", "Save Assignment?")" | |
| Message="@AppLocalization.Text("task.saveAssignmentConfirm", "Are you sure you want to save this task assignment?")" | |
| ConfirmText="@AppLocalization.Text("issue.yesSave", "Yes, Save")" | |
| Icon="user-check" | |
| OnConfirm="ConfirmSaveAssignment" | |
| OnCancel="CloseConfirmDialog" /> | |
| @code { | |
| private bool isLoading = true; | |
| private bool isSaving = false; | |
| private bool canSaveAssignments = false; | |
| private bool showConfirmDialog = false; | |
| private string? statusMessage; | |
| private string? successMessage; | |
| private bool isError = false; | |
| private List<TaskDto> tasks = new(); | |
| private List<ProjectDto> projects = new(); | |
| private List<UserDto> allUsers = new(); | |
| private List<UserDto> projectMembers = new(); | |
| private List<RoleDto> roles = new(); | |
| private long? adminRoleId; | |
| private long? employeeRoleId; | |
| private bool isAdminUser; | |
| private long selectedProjectId = 0; | |
| private long selectedProjectIdInput = 0; | |
| private long selectedRoleId = 0; | |
| private string taskSearchInput = ""; | |
| private string taskSearch = ""; | |
| private string userSearchInput = ""; | |
| private string userSearch = ""; | |
| private TaskDto? SelectedTask { get; set; } | |
| private long? TargetAssigneeId { get; set; } | |
| protected override async Task OnInitializedAsync() | |
| { | |
| var authState = await AuthStateProvider.GetAuthenticationStateAsync(); | |
| isAdminUser = authState.User.IsInRole("Admin"); | |
| canSaveAssignments = authState.User.IsInRole("Admin") || await MenuAuthorization.HasAccessCodeAsync(authState.User, "Tasks_Update"); | |
| var client = ApiClient.CreateClient(); | |
| try | |
| { | |
| var tasksTask = client.GetFromJsonAsync<List<TaskDto>>("Task", Serialization.CaseInsensitive); | |
| var projectsTask = client.GetFromJsonAsync<List<ProjectDto>>("Project", Serialization.CaseInsensitive); | |
| var usersTask = client.GetFromJsonAsync<List<UserDto>>("User", Serialization.CaseInsensitive); | |
| var rolesTask = client.GetFromJsonAsync<List<RoleDto>>("Role", Serialization.CaseInsensitive); | |
| await Task.WhenAll(tasksTask, projectsTask, usersTask, rolesTask); | |
| tasks = tasksTask.Result ?? new(); | |
| projects = projectsTask.Result ?? new(); | |
| allUsers = usersTask.Result ?? new(); | |
| var allLoadedRoles = rolesTask.Result ?? new(); | |
| var adminRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)); | |
| var employeeRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Employee", StringComparison.OrdinalIgnoreCase)); | |
| adminRoleId = adminRole?.Id; | |
| employeeRoleId = employeeRole?.Id; | |
| roles = allLoadedRoles.Where(r => !string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)).ToList(); | |
| if (!isAdminUser && employeeRoleId.HasValue) | |
| { | |
| selectedRoleId = employeeRoleId.Value; | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error loading initial data: {ex.Message}"); | |
| } | |
| finally | |
| { | |
| isLoading = false; | |
| } | |
| } | |
| protected override async Task OnAfterRenderAsync(bool firstRender) | |
| { | |
| await JS.InvokeVoidAsync("initIcons"); | |
| } | |
| private IEnumerable<TaskDto> FilteredTasks | |
| { | |
| get | |
| { | |
| var result = tasks.AsEnumerable(); | |
| if (selectedProjectId > 0) | |
| { | |
| result = result.Where(t => t.ProjectId == selectedProjectId); | |
| } | |
| if (!string.IsNullOrWhiteSpace(taskSearch)) | |
| { | |
| result = result.Where(t => t.Title.Contains(taskSearch, StringComparison.OrdinalIgnoreCase) | |
| || (t.Description != null && t.Description.Contains(taskSearch, StringComparison.OrdinalIgnoreCase))); | |
| } | |
| return result; | |
| } | |
| } | |
| private IEnumerable<UserDto> FilteredUsers | |
| { | |
| get | |
| { | |
| var result = projectMembers.Where(u => u.IsActive); | |
| if (adminRoleId.HasValue) | |
| { | |
| result = result.Where(u => u.RoleId != adminRoleId.Value); | |
| } | |
| if (isAdminUser && selectedRoleId > 0) | |
| { | |
| result = result.Where(u => u.RoleId == selectedRoleId); | |
| } | |
| else if (!isAdminUser && employeeRoleId.HasValue) | |
| { | |
| result = result.Where(u => u.RoleId == employeeRoleId.Value); | |
| } | |
| if (!string.IsNullOrWhiteSpace(userSearch)) | |
| { | |
| result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(userSearch, StringComparison.OrdinalIgnoreCase) | |
| || u.Username.Contains(userSearch, StringComparison.OrdinalIgnoreCase)); | |
| } | |
| return result; | |
| } | |
| } | |
| private void ApplyTaskSearch() | |
| { | |
| taskSearch = taskSearchInput; | |
| } | |
| private void ApplyUserSearch() | |
| { | |
| userSearch = userSearchInput; | |
| } | |
| private void ApplyProjectFilter() | |
| { | |
| selectedProjectId = selectedProjectIdInput; | |
| SelectedTask = null; | |
| TargetAssigneeId = null; | |
| projectMembers.Clear(); | |
| statusMessage = null; | |
| } | |
| private void ResetProjectFilter() | |
| { | |
| selectedProjectIdInput = 0; | |
| selectedProjectId = 0; | |
| SelectedTask = null; | |
| TargetAssigneeId = null; | |
| projectMembers.Clear(); | |
| statusMessage = null; | |
| } | |
| private void ResetTaskSearch() | |
| { | |
| taskSearchInput = ""; | |
| taskSearch = ""; | |
| } | |
| private void ResetUserSearch() | |
| { | |
| userSearchInput = ""; | |
| userSearch = ""; | |
| selectedRoleId = 0; | |
| } | |
| private async Task SelectTask(TaskDto task) | |
| { | |
| SelectedTask = task; | |
| TargetAssigneeId = task.AssignedTo; | |
| statusMessage = null; | |
| await LoadProjectMembersAsync(task.ProjectId); | |
| } | |
| private void SetTargetAssignee(long? userId) | |
| { | |
| TargetAssigneeId = userId; | |
| statusMessage = null; | |
| } | |
| private void RequestSaveConfirmation() | |
| { | |
| if (SelectedTask == null) return; | |
| showConfirmDialog = true; | |
| } | |
| private void CloseConfirmDialog() | |
| { | |
| showConfirmDialog = false; | |
| } | |
| private async Task ConfirmSaveAssignment() | |
| { | |
| showConfirmDialog = false; | |
| await SaveAssignment(); | |
| } | |
| private async Task LoadProjectMembersAsync(long projectId) | |
| { | |
| projectMembers.Clear(); | |
| if (projectId <= 0) | |
| { | |
| return; | |
| } | |
| var client = ApiClient.CreateClient(); | |
| try | |
| { | |
| var membersResult = await client.GetFromJsonAsync<Result<IEnumerable<UserDto>>>($"Project/{projectId}/members", Serialization.CaseInsensitive); | |
| if (membersResult?.IsSuccess == true && membersResult.Value != null) | |
| { | |
| projectMembers = membersResult.Value.ToList(); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error loading project members: {ex.Message}"); | |
| statusMessage = "Failed to load project members."; | |
| isError = true; | |
| } | |
| finally | |
| { | |
| await InvokeAsync(StateHasChanged); | |
| await JS.InvokeVoidAsync("initIcons"); | |
| } | |
| } | |
| private async Task SaveAssignment() | |
| { | |
| if (SelectedTask == null) return; | |
| isSaving = true; | |
| statusMessage = null; | |
| var client = ApiClient.CreateClient(); | |
| try | |
| { | |
| var dto = new UpdateTaskDto | |
| { | |
| Title = SelectedTask.Title, | |
| Description = SelectedTask.Description, | |
| StatusId = SelectedTask.StatusId, | |
| PriorityId = SelectedTask.PriorityId, | |
| DueDate = SelectedTask.DueDate, | |
| AssignedTo = TargetAssigneeId | |
| }; | |
| var response = await client.PutAsJsonAsync($"Task/{SelectedTask.Id}", dto); | |
| if (response.IsSuccessStatusCode) | |
| { | |
| SelectedTask.AssignedTo = TargetAssigneeId; | |
| statusMessage = "Task assignee updated successfully!"; | |
| successMessage = "Assignment saved successfully!"; | |
| isError = false; | |
| Navigation.NavigateTo("/tasks/all"); | |
| } | |
| else | |
| { | |
| statusMessage = "Failed to update assignee."; | |
| isError = true; | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| statusMessage = $"Error: {ex.Message}"; | |
| isError = true; | |
| } | |
| finally | |
| { | |
| isSaving = false; | |
| await JS.InvokeVoidAsync("initIcons"); | |
| } | |
| } | |
| 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", | |
| _ => "-" | |
| }; | |
| } | |