User commited on
Commit
086aaf2
·
1 Parent(s): 5723c42

add: view archived tasks button

Browse files
TaskTrackingSystem.WebApi/Features/Task/TaskController.cs CHANGED
@@ -30,6 +30,13 @@ namespace TaskTrackingSystem.WebApi.Features.Task
30
  return Ok(tasks);
31
  }
32
 
 
 
 
 
 
 
 
33
  [HttpGet("{id}")]
34
  public async Task<ActionResult<TaskDto>> GetTask(long id)
35
  {
 
30
  return Ok(tasks);
31
  }
32
 
33
+ [HttpGet("archived")]
34
+ public async Task<ActionResult<IEnumerable<TaskDto>>> GetArchivedTasks()
35
+ {
36
+ var tasks = await _taskService.GetArchivedTasksAsync(User.GetRoleName(), User.GetUserId());
37
+ return Ok(tasks);
38
+ }
39
+
40
  [HttpGet("{id}")]
41
  public async Task<ActionResult<TaskDto>> GetTask(long id)
42
  {
TaskTrackingSystem.WebApi/Features/Task/TaskService.cs CHANGED
@@ -50,6 +50,34 @@ namespace TaskTrackingSystem.WebApi.Features.Task
50
  }).ToListAsync();
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  public async Task<TaskDto?> GetTaskByIdAsync(long id, string roleName, long currentUserId)
54
  {
55
  var task = await BuildAccessibleTaskQuery(roleName, currentUserId)
@@ -338,9 +366,9 @@ namespace TaskTrackingSystem.WebApi.Features.Task
338
  return Result<int>.Success(tasksToArchive.Count);
339
  }
340
 
341
- private IQueryable<TaskTrackingSystem.Database.AppDbContextModels.Task> BuildAccessibleTaskQuery(string roleName, long currentUserId)
342
  {
343
- var query = _db.Tasks.Where(t => t.IsDeleted != true && !t.IsArchived);
344
 
345
  if (IsAdmin(roleName))
346
  {
 
50
  }).ToListAsync();
51
  }
52
 
53
+ public async Task<IEnumerable<TaskDto>> GetArchivedTasksAsync(string roleName, long currentUserId)
54
+ {
55
+ return await BuildAccessibleTaskQuery(roleName, currentUserId, includeArchived: true)
56
+ .Where(t => t.IsArchived)
57
+ .OrderByDescending(t => t.UpdatedAt ?? t.CreatedAt ?? DateTime.UtcNow)
58
+ .Select(t => new TaskDto
59
+ {
60
+ Id = t.Id,
61
+ Title = t.Title,
62
+ Description = t.Description,
63
+ ProjectId = t.ProjectId,
64
+ StatusId = t.StatusId,
65
+ PriorityId = t.PriorityId,
66
+ AssignedTo = t.AssignedTo,
67
+ AssignedBy = t.AssignedBy,
68
+ EstimatedHours = t.EstimatedHours,
69
+ ActualHours = t.ActualHours,
70
+ DueDate = t.DueDate,
71
+ CreatedAt = t.CreatedAt ?? DateTime.UtcNow,
72
+ IsArchived = t.IsArchived,
73
+ CompletedAt = t.TaskHistories
74
+ .Where(th => th.NewStatusId == AppTaskStatus.Done)
75
+ .OrderBy(th => th.CreatedAt)
76
+ .Select(th => th.CreatedAt)
77
+ .FirstOrDefault()
78
+ }).ToListAsync();
79
+ }
80
+
81
  public async Task<TaskDto?> GetTaskByIdAsync(long id, string roleName, long currentUserId)
82
  {
83
  var task = await BuildAccessibleTaskQuery(roleName, currentUserId)
 
366
  return Result<int>.Success(tasksToArchive.Count);
367
  }
368
 
369
+ private IQueryable<TaskTrackingSystem.Database.AppDbContextModels.Task> BuildAccessibleTaskQuery(string roleName, long currentUserId, bool includeArchived = false)
370
  {
371
+ var query = _db.Tasks.Where(t => t.IsDeleted != true && (includeArchived || !t.IsArchived));
372
 
373
  if (IsAdmin(roleName))
374
  {
TaskTrackingSystem.WebApp/Components/Pages/KanbanBoard.razor CHANGED
@@ -37,14 +37,18 @@
37
  <option value="@p.Id">@p.Name</option>
38
  }
39
  </select>
40
- @if (canUpdateTask)
 
 
 
 
41
  {
42
  <button @onclick="RequestArchiveDoneConfirmation" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition-colors shadow-sm">
43
  <i data-lucide="archive" class="h-4 w-4 mr-2 text-slate-500"></i>
44
  Archive Done
45
  </button>
46
  }
47
- @if (canCreateTask)
48
  {
49
  <button @onclick="OpenCreateTaskModal" class="inline-flex items-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-violet-700 transition-colors shadow-sm">
50
  <i data-lucide="plus" class="h-4 w-4 mr-2"></i>
@@ -73,8 +77,11 @@
73
  else
74
  {
75
  <!-- Kanban Columns -->
76
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
77
- @foreach (var column in columns)
 
 
 
78
  {
79
  var tasksInColumn = VisibleTasks.Where(t => t.StatusId == column.StatusId).ToList();
80
  <div @key="column.StatusId" class="flex flex-col rounded-xl border border-slate-200 bg-slate-50/50 overflow-hidden min-h-[400px]">
@@ -88,7 +95,7 @@
88
  </div>
89
 
90
  <!-- Cards -->
91
- <div class="flex-1 p-3 space-y-3 overflow-y-auto transition-colors @(dropTargetStatus == column.StatusId ? "bg-slate-100" : "")"
92
  @ondrop="() => HandleDrop(column.StatusId)"
93
  @ondragenter="() => HandleDragEnter(column.StatusId)"
94
  @ondragleave="HandleDragLeave"
@@ -98,9 +105,9 @@
98
  {
99
  @foreach (var task in tasksInColumn)
100
  {
101
- <div @key="task.Id" class="select-none rounded-lg border border-slate-200 bg-white p-4 shadow-sm hover:shadow-md transition-shadow @(canUpdateTask ? "cursor-grab active:cursor-grabbing" : "cursor-default")"
102
  data-task-id="@task.Id"
103
- draggable="@(canUpdateTask ? "true" : "false")"
104
  @ondragstart="() => HandleDragStart(task)"
105
  @ondragend="HandleDragEnd">
106
  <div class="flex items-start justify-between">
@@ -113,6 +120,10 @@
113
  }
114
  <div class="flex items-center justify-between mt-3 pt-3 border-t border-slate-50">
115
  <span class="text-xs text-slate-400">Due @DisplayFormats.Date(task.DueDate)</span>
 
 
 
 
116
  </div>
117
  </div>
118
  }
@@ -222,6 +233,7 @@
222
  private bool isManager = false;
223
  private bool canCreateTask = false;
224
  private bool canUpdateTask = false;
 
225
  private long currentUserId = 0;
226
  private ProjectDto? project;
227
  private bool showTaskModal = false;
@@ -268,6 +280,11 @@
268
  ? tasks.Where(t => t.ProjectId == selectedProjectId)
269
  : tasks;
270
 
 
 
 
 
 
271
  private Task HandleDragStart(TaskDto task)
272
  {
273
  if (!canUpdateTask)
@@ -275,6 +292,11 @@
275
  return Task.CompletedTask;
276
  }
277
 
 
 
 
 
 
278
  draggedTask = task;
279
  dropTargetStatus = null;
280
  return Task.CompletedTask;
@@ -348,7 +370,7 @@
348
  var idStr = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
349
  if (idStr != null) long.TryParse(idStr, out currentUserId);
350
 
351
- var tasksTask = client.GetFromJsonAsync<List<TaskDto>>("Task", Serialization.CaseInsensitive);
352
  var projectsTask = client.GetFromJsonAsync<List<ProjectDto>>("Project", Serialization.CaseInsensitive);
353
  await System.Threading.Tasks.Task.WhenAll(tasksTask, projectsTask);
354
 
@@ -415,6 +437,15 @@
415
 
416
  private void GoBack() => Navigation.NavigateTo("/projects/all");
417
 
 
 
 
 
 
 
 
 
 
418
  private void OnProjectFilterChanged(ChangeEventArgs e)
419
  {
420
  selectedProjectId = long.TryParse(e.Value?.ToString(), out var value) ? value : 0;
 
37
  <option value="@p.Id">@p.Name</option>
38
  }
39
  </select>
40
+ <button @onclick="ToggleArchivedView" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition-colors shadow-sm">
41
+ <i data-lucide="@(showArchivedTasks ? "layout-grid" : "archive")" class="h-4 w-4 mr-2 text-slate-500"></i>
42
+ @(showArchivedTasks ? "Back to Board" : "Archived Tasks")
43
+ </button>
44
+ @if (canUpdateTask && !showArchivedTasks)
45
  {
46
  <button @onclick="RequestArchiveDoneConfirmation" class="inline-flex items-center rounded-lg border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition-colors shadow-sm">
47
  <i data-lucide="archive" class="h-4 w-4 mr-2 text-slate-500"></i>
48
  Archive Done
49
  </button>
50
  }
51
+ @if (canCreateTask && !showArchivedTasks)
52
  {
53
  <button @onclick="OpenCreateTaskModal" class="inline-flex items-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-violet-700 transition-colors shadow-sm">
54
  <i data-lucide="plus" class="h-4 w-4 mr-2"></i>
 
77
  else
78
  {
79
  <!-- Kanban Columns -->
80
+ <div class="mb-2 text-sm text-slate-500">
81
+ @(showArchivedTasks ? "Viewing archived tasks." : "Drag tasks between columns to update their status.")
82
+ </div>
83
+ <div class="grid grid-cols-1 @(showArchivedTasks ? "md:grid-cols-1" : "md:grid-cols-3") gap-6">
84
+ @foreach (var column in VisibleColumns)
85
  {
86
  var tasksInColumn = VisibleTasks.Where(t => t.StatusId == column.StatusId).ToList();
87
  <div @key="column.StatusId" class="flex flex-col rounded-xl border border-slate-200 bg-slate-50/50 overflow-hidden min-h-[400px]">
 
95
  </div>
96
 
97
  <!-- Cards -->
98
+ <div class="flex-1 p-3 space-y-3 overflow-y-auto transition-colors @((!showArchivedTasks && dropTargetStatus == column.StatusId) ? "bg-slate-100" : "")"
99
  @ondrop="() => HandleDrop(column.StatusId)"
100
  @ondragenter="() => HandleDragEnter(column.StatusId)"
101
  @ondragleave="HandleDragLeave"
 
105
  {
106
  @foreach (var task in tasksInColumn)
107
  {
108
+ <div @key="task.Id" class="select-none rounded-lg border border-slate-200 bg-white p-4 shadow-sm hover:shadow-md transition-shadow @((canUpdateTask && !showArchivedTasks) ? "cursor-grab active:cursor-grabbing" : "cursor-default")"
109
  data-task-id="@task.Id"
110
+ draggable="@((canUpdateTask && !showArchivedTasks).ToString().ToLowerInvariant())"
111
  @ondragstart="() => HandleDragStart(task)"
112
  @ondragend="HandleDragEnd">
113
  <div class="flex items-start justify-between">
 
120
  }
121
  <div class="flex items-center justify-between mt-3 pt-3 border-t border-slate-50">
122
  <span class="text-xs text-slate-400">Due @DisplayFormats.Date(task.DueDate)</span>
123
+ @if (showArchivedTasks)
124
+ {
125
+ <span class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-500">Archived</span>
126
+ }
127
  </div>
128
  </div>
129
  }
 
233
  private bool isManager = false;
234
  private bool canCreateTask = false;
235
  private bool canUpdateTask = false;
236
+ private bool showArchivedTasks = false;
237
  private long currentUserId = 0;
238
  private ProjectDto? project;
239
  private bool showTaskModal = false;
 
280
  ? tasks.Where(t => t.ProjectId == selectedProjectId)
281
  : tasks;
282
 
283
+ private IEnumerable<KanbanColumn> VisibleColumns =>
284
+ showArchivedTasks
285
+ ? columns.Where(column => column.StatusId == AppTaskStatus.Done)
286
+ : columns;
287
+
288
  private Task HandleDragStart(TaskDto task)
289
  {
290
  if (!canUpdateTask)
 
292
  return Task.CompletedTask;
293
  }
294
 
295
+ if (showArchivedTasks)
296
+ {
297
+ return Task.CompletedTask;
298
+ }
299
+
300
  draggedTask = task;
301
  dropTargetStatus = null;
302
  return Task.CompletedTask;
 
370
  var idStr = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
371
  if (idStr != null) long.TryParse(idStr, out currentUserId);
372
 
373
+ var tasksTask = client.GetFromJsonAsync<List<TaskDto>>(showArchivedTasks ? "Task/archived" : "Task", Serialization.CaseInsensitive);
374
  var projectsTask = client.GetFromJsonAsync<List<ProjectDto>>("Project", Serialization.CaseInsensitive);
375
  await System.Threading.Tasks.Task.WhenAll(tasksTask, projectsTask);
376
 
 
437
 
438
  private void GoBack() => Navigation.NavigateTo("/projects/all");
439
 
440
+ private async Task ToggleArchivedView()
441
+ {
442
+ showArchivedTasks = !showArchivedTasks;
443
+ draggedTask = null;
444
+ dropTargetStatus = null;
445
+ boardError = null;
446
+ await LoadTasks();
447
+ }
448
+
449
  private void OnProjectFilterChanged(ChangeEventArgs e)
450
  {
451
  selectedProjectId = long.TryParse(e.Value?.ToString(), out var value) ? value : 0;