User commited on
Commit
fa69bf0
·
1 Parent(s): d9a9170

fix: small fixes on project assign, task assign

Browse files
TaskTrackingSystem.WebApi/Features/Dashboard/DashboardService.cs CHANGED
@@ -20,13 +20,17 @@ namespace TaskTrackingSystem.WebApi.Features.Dashboard
20
 
21
  public async Task<Result<DashboardSummaryDto>> GetSummaryAsync(string roleName, long currentUserId)
22
  {
23
- var elevated = IsElevated(roleName);
24
  var projects = BuildAccessibleProjectQuery(roleName, currentUserId);
25
  var tasks = BuildAccessibleTaskQuery(roleName, currentUserId);
26
 
27
- var totalUsers = elevated
28
  ? await _db.Users.CountAsync(u => !u.IsDeleted)
29
- : await _db.Users.CountAsync(u => u.Id == currentUserId && !u.IsDeleted);
 
 
 
 
 
30
 
31
  var activeProjectsCount = await projects.CountAsync();
32
  var pendingTasksCount = await tasks.CountAsync(t => t.StatusId != 3);
@@ -119,11 +123,19 @@ namespace TaskTrackingSystem.WebApi.Features.Dashboard
119
  {
120
  var query = _db.Tasks.Where(t => t.IsDeleted != true);
121
 
122
- if (IsElevated(roleName))
123
  {
124
  return query;
125
  }
126
 
 
 
 
 
 
 
 
 
127
  return query.Where(t =>
128
  t.AssignedTo == currentUserId ||
129
  t.CreatedBy == currentUserId);
@@ -133,7 +145,7 @@ namespace TaskTrackingSystem.WebApi.Features.Dashboard
133
  {
134
  var query = _db.Projects.Where(p => p.IsDeleted != true);
135
 
136
- if (IsElevated(roleName))
137
  {
138
  return query;
139
  }
@@ -143,10 +155,9 @@ namespace TaskTrackingSystem.WebApi.Features.Dashboard
143
  p.ProjectMembers.Any(pm => pm.UserId == currentUserId));
144
  }
145
 
146
- private static bool IsElevated(string roleName)
147
  {
148
- return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase) ||
149
- roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase);
150
  }
151
  }
152
  }
 
20
 
21
  public async Task<Result<DashboardSummaryDto>> GetSummaryAsync(string roleName, long currentUserId)
22
  {
 
23
  var projects = BuildAccessibleProjectQuery(roleName, currentUserId);
24
  var tasks = BuildAccessibleTaskQuery(roleName, currentUserId);
25
 
26
+ var totalUsers = IsAdmin(roleName)
27
  ? await _db.Users.CountAsync(u => !u.IsDeleted)
28
+ : await _db.Users
29
+ .Where(u => !u.IsDeleted &&
30
+ projects.SelectMany(p => p.ProjectMembers.Select(pm => pm.UserId))
31
+ .Distinct()
32
+ .Contains(u.Id))
33
+ .CountAsync();
34
 
35
  var activeProjectsCount = await projects.CountAsync();
36
  var pendingTasksCount = await tasks.CountAsync(t => t.StatusId != 3);
 
123
  {
124
  var query = _db.Tasks.Where(t => t.IsDeleted != true);
125
 
126
+ if (IsAdmin(roleName))
127
  {
128
  return query;
129
  }
130
 
131
+ if (roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase))
132
+ {
133
+ return query.Where(t =>
134
+ t.AssignedTo == currentUserId ||
135
+ t.CreatedBy == currentUserId ||
136
+ t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId));
137
+ }
138
+
139
  return query.Where(t =>
140
  t.AssignedTo == currentUserId ||
141
  t.CreatedBy == currentUserId);
 
145
  {
146
  var query = _db.Projects.Where(p => p.IsDeleted != true);
147
 
148
+ if (IsAdmin(roleName))
149
  {
150
  return query;
151
  }
 
155
  p.ProjectMembers.Any(pm => pm.UserId == currentUserId));
156
  }
157
 
158
+ private static bool IsAdmin(string roleName)
159
  {
160
+ return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase);
 
161
  }
162
  }
163
  }
TaskTrackingSystem.WebApi/Features/Project/ProjectController.cs CHANGED
@@ -61,8 +61,8 @@ namespace TaskTrackingSystem.WebApi.Features.Project
61
  return Forbid();
62
  }
63
 
64
- long? currentUserId = User.GetUserId();
65
- var result = await _projectService.UpdateProjectAsync(id, updateProjectDto, currentUserId);
66
  return StatusCode(result.StatusCode, result);
67
  }
68
 
@@ -74,7 +74,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
74
  return Forbid();
75
  }
76
 
77
- var result = await _projectService.SoftDeleteProjectAsync(id);
78
  return StatusCode(result.StatusCode, result);
79
  }
80
 
@@ -93,7 +93,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
93
  return Forbid();
94
  }
95
 
96
- var result = await _projectService.AssignMembersToProjectAsync(id, dto, User.GetUserId());
97
  if (!result.IsSuccess)
98
  {
99
  return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
@@ -109,7 +109,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
109
  return Forbid();
110
  }
111
 
112
- var result = await _projectService.RemoveMemberFromProjectAsync(id, userId, User.GetUserId());
113
  if (!result.IsSuccess)
114
  {
115
  return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
 
61
  return Forbid();
62
  }
63
 
64
+ var currentUserId = User.GetUserId();
65
+ var result = await _projectService.UpdateProjectAsync(id, updateProjectDto, User.GetRoleName(), currentUserId);
66
  return StatusCode(result.StatusCode, result);
67
  }
68
 
 
74
  return Forbid();
75
  }
76
 
77
+ var result = await _projectService.SoftDeleteProjectAsync(id, User.GetRoleName(), User.GetUserId());
78
  return StatusCode(result.StatusCode, result);
79
  }
80
 
 
93
  return Forbid();
94
  }
95
 
96
+ var result = await _projectService.AssignMembersToProjectAsync(id, dto, User.GetRoleName(), User.GetUserId());
97
  if (!result.IsSuccess)
98
  {
99
  return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
 
109
  return Forbid();
110
  }
111
 
112
+ var result = await _projectService.RemoveMemberFromProjectAsync(id, userId, User.GetRoleName(), User.GetUserId());
113
  if (!result.IsSuccess)
114
  {
115
  return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
TaskTrackingSystem.WebApi/Features/Project/ProjectService.cs CHANGED
@@ -96,7 +96,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
96
  return Result<ProjectDto>.Success(resultDto, 201);
97
  }
98
 
99
- public async Task<Result> UpdateProjectAsync(long id, UpdateProjectDto dto, long? currentUserId = null)
100
  {
101
  if (string.IsNullOrWhiteSpace(dto.Name))
102
  {
@@ -106,6 +106,11 @@ namespace TaskTrackingSystem.WebApi.Features.Project
106
  var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
107
  if (project == null) return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
108
 
 
 
 
 
 
109
  project.Name = dto.Name;
110
  project.Description = dto.Description;
111
  project.StartDate = dto.StartDate;
@@ -119,11 +124,16 @@ namespace TaskTrackingSystem.WebApi.Features.Project
119
  return Result.Success(200);
120
  }
121
 
122
- public async Task<Result> SoftDeleteProjectAsync(long id)
123
  {
124
  var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
125
  if (project == null) return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
126
 
 
 
 
 
 
127
  project.IsDeleted = true;
128
  _db.Projects.Update(project);
129
  await _db.SaveChangesAsync();
@@ -159,7 +169,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
159
  return Result<IEnumerable<UserDto>>.Success(members);
160
  }
161
 
162
- public async Task<Result> AssignMembersToProjectAsync(long projectId, AssignMembersDto dto, long? currentUserId = null)
163
  {
164
  var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
165
  if (!projectExists)
@@ -167,6 +177,11 @@ namespace TaskTrackingSystem.WebApi.Features.Project
167
  return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
168
  }
169
 
 
 
 
 
 
170
  if (dto.UserIds == null)
171
  {
172
  return Result.Failure(ResultMessages.UserIdsCannotBeNull, 400);
@@ -212,7 +227,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
212
  return Result.Success(200);
213
  }
214
 
215
- public async Task<Result> RemoveMemberFromProjectAsync(long projectId, long userId, long? currentUserId = null)
216
  {
217
  var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
218
  if (!projectExists)
@@ -220,6 +235,11 @@ namespace TaskTrackingSystem.WebApi.Features.Project
220
  return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
221
  }
222
 
 
 
 
 
 
223
  var member = await _db.ProjectMembers
224
  .FirstOrDefaultAsync(pm => pm.ProjectId == projectId && pm.UserId == userId);
225
 
@@ -271,7 +291,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
271
  {
272
  var query = _db.Projects.Where(p => p.IsDeleted != true);
273
 
274
- if (IsElevated(roleName))
275
  {
276
  return query;
277
  }
@@ -283,7 +303,7 @@ namespace TaskTrackingSystem.WebApi.Features.Project
283
 
284
  private async Task<bool> IsProjectAccessibleAsync(long projectId, string roleName, long currentUserId)
285
  {
286
- if (IsElevated(roleName))
287
  {
288
  return await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
289
  }
@@ -294,10 +314,9 @@ namespace TaskTrackingSystem.WebApi.Features.Project
294
  (p.CreatedById == currentUserId || p.ProjectMembers.Any(pm => pm.UserId == currentUserId)));
295
  }
296
 
297
- private static bool IsElevated(string roleName)
298
  {
299
- return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase) ||
300
- roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase);
301
  }
302
  }
303
  }
 
96
  return Result<ProjectDto>.Success(resultDto, 201);
97
  }
98
 
99
+ public async Task<Result> UpdateProjectAsync(long id, UpdateProjectDto dto, string roleName, long currentUserId)
100
  {
101
  if (string.IsNullOrWhiteSpace(dto.Name))
102
  {
 
106
  var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
107
  if (project == null) return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
108
 
109
+ if (!await IsProjectAccessibleAsync(id, roleName, currentUserId))
110
+ {
111
+ return Result.Failure("You do not have access to this project.", 403);
112
+ }
113
+
114
  project.Name = dto.Name;
115
  project.Description = dto.Description;
116
  project.StartDate = dto.StartDate;
 
124
  return Result.Success(200);
125
  }
126
 
127
+ public async Task<Result> SoftDeleteProjectAsync(long id, string roleName, long currentUserId)
128
  {
129
  var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
130
  if (project == null) return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
131
 
132
+ if (!await IsProjectAccessibleAsync(id, roleName, currentUserId))
133
+ {
134
+ return Result.Failure("You do not have access to this project.", 403);
135
+ }
136
+
137
  project.IsDeleted = true;
138
  _db.Projects.Update(project);
139
  await _db.SaveChangesAsync();
 
169
  return Result<IEnumerable<UserDto>>.Success(members);
170
  }
171
 
172
+ public async Task<Result> AssignMembersToProjectAsync(long projectId, AssignMembersDto dto, string roleName, long currentUserId)
173
  {
174
  var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
175
  if (!projectExists)
 
177
  return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
178
  }
179
 
180
+ if (!await IsProjectAccessibleAsync(projectId, roleName, currentUserId))
181
+ {
182
+ return Result.Failure("You do not have access to this project.", 403);
183
+ }
184
+
185
  if (dto.UserIds == null)
186
  {
187
  return Result.Failure(ResultMessages.UserIdsCannotBeNull, 400);
 
227
  return Result.Success(200);
228
  }
229
 
230
+ public async Task<Result> RemoveMemberFromProjectAsync(long projectId, long userId, string roleName, long currentUserId)
231
  {
232
  var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
233
  if (!projectExists)
 
235
  return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
236
  }
237
 
238
+ if (!await IsProjectAccessibleAsync(projectId, roleName, currentUserId))
239
+ {
240
+ return Result.Failure("You do not have access to this project.", 403);
241
+ }
242
+
243
  var member = await _db.ProjectMembers
244
  .FirstOrDefaultAsync(pm => pm.ProjectId == projectId && pm.UserId == userId);
245
 
 
291
  {
292
  var query = _db.Projects.Where(p => p.IsDeleted != true);
293
 
294
+ if (IsAdmin(roleName))
295
  {
296
  return query;
297
  }
 
303
 
304
  private async Task<bool> IsProjectAccessibleAsync(long projectId, string roleName, long currentUserId)
305
  {
306
+ if (IsAdmin(roleName))
307
  {
308
  return await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
309
  }
 
314
  (p.CreatedById == currentUserId || p.ProjectMembers.Any(pm => pm.UserId == currentUserId)));
315
  }
316
 
317
+ private static bool IsAdmin(string roleName)
318
  {
319
+ return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase);
 
320
  }
321
  }
322
  }
TaskTrackingSystem.WebApi/Features/Report/ReportController.cs CHANGED
@@ -5,6 +5,7 @@ using System.Collections.Generic;
5
  using System.Threading.Tasks;
6
  using TaskTrackingSystem.Shared;
7
  using TaskTrackingSystem.Shared.Models.Report;
 
8
 
9
  namespace TaskTrackingSystem.WebApi.Features.Report
10
  {
@@ -29,14 +30,14 @@ namespace TaskTrackingSystem.WebApi.Features.Report
29
  [FromQuery] string? status,
30
  [FromQuery] int? projectId)
31
  {
32
- var result = await _reportService.GetTasksReportAsync(startDate, endDate, status, projectId);
33
  return StatusCode(result.StatusCode, result);
34
  }
35
 
36
  [HttpGet("user-productivity")]
37
  public async Task<ActionResult<Result<IEnumerable<UserProductivityDto>>>> GetUserProductivityReport()
38
  {
39
- var result = await _reportService.GetUserProductivityReportAsync();
40
  return StatusCode(result.StatusCode, result);
41
  }
42
 
@@ -48,7 +49,7 @@ namespace TaskTrackingSystem.WebApi.Features.Report
48
  [FromQuery] long? statusId,
49
  [FromQuery] long? projectId)
50
  {
51
- var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId);
52
  return StatusCode(result.StatusCode, result);
53
  }
54
 
@@ -58,7 +59,7 @@ namespace TaskTrackingSystem.WebApi.Features.Report
58
  [FromQuery] long? statusId,
59
  [FromQuery] long? projectId)
60
  {
61
- var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId);
62
  if (!result.IsSuccess || result.Value == null)
63
  return BadRequest(new { message = result.ErrorMessage });
64
  var bytes = _reportService.ExportTaskStatusSummaryToExcel(result.Value);
@@ -72,14 +73,14 @@ namespace TaskTrackingSystem.WebApi.Features.Report
72
  public async Task<ActionResult<Result<IEnumerable<TeamProductivityReportDto>>>> GetTeamProductivity(
73
  [FromQuery] string? search)
74
  {
75
- var result = await _reportService.GetTeamProductivityAsync(search);
76
  return StatusCode(result.StatusCode, result);
77
  }
78
 
79
  [HttpGet("team-productivity/excel")]
80
  public async Task<IActionResult> DownloadTeamProductivityExcel([FromQuery] string? search)
81
  {
82
- var result = await _reportService.GetTeamProductivityAsync(search);
83
  if (!result.IsSuccess || result.Value == null)
84
  return BadRequest(new { message = result.ErrorMessage });
85
  var bytes = _reportService.ExportTeamProductivityToExcel(result.Value);
@@ -94,7 +95,7 @@ namespace TaskTrackingSystem.WebApi.Features.Report
94
  [FromQuery] string? search,
95
  [FromQuery] long? projectId)
96
  {
97
- var result = await _reportService.GetOverdueCriticalTasksAsync(search, projectId);
98
  return StatusCode(result.StatusCode, result);
99
  }
100
 
@@ -103,7 +104,7 @@ namespace TaskTrackingSystem.WebApi.Features.Report
103
  [FromQuery] string? search,
104
  [FromQuery] long? projectId)
105
  {
106
- var result = await _reportService.GetOverdueCriticalTasksAsync(search, projectId);
107
  if (!result.IsSuccess || result.Value == null)
108
  return BadRequest(new { message = result.ErrorMessage });
109
  var bytes = _reportService.ExportOverdueCriticalToExcel(result.Value);
@@ -119,7 +120,7 @@ namespace TaskTrackingSystem.WebApi.Features.Report
119
  [FromQuery] long? projectId,
120
  [FromQuery] long? statusId)
121
  {
122
- var result = await _reportService.GetTimeTrackingReportAsync(search, projectId, statusId);
123
  return StatusCode(result.StatusCode, result);
124
  }
125
  }
 
5
  using System.Threading.Tasks;
6
  using TaskTrackingSystem.Shared;
7
  using TaskTrackingSystem.Shared.Models.Report;
8
+ using TaskTrackingSystem.WebApi.Infrastructure;
9
 
10
  namespace TaskTrackingSystem.WebApi.Features.Report
11
  {
 
30
  [FromQuery] string? status,
31
  [FromQuery] int? projectId)
32
  {
33
+ var result = await _reportService.GetTasksReportAsync(startDate, endDate, status, projectId, User.GetRoleName(), User.GetUserId());
34
  return StatusCode(result.StatusCode, result);
35
  }
36
 
37
  [HttpGet("user-productivity")]
38
  public async Task<ActionResult<Result<IEnumerable<UserProductivityDto>>>> GetUserProductivityReport()
39
  {
40
+ var result = await _reportService.GetUserProductivityReportAsync(User.GetRoleName(), User.GetUserId());
41
  return StatusCode(result.StatusCode, result);
42
  }
43
 
 
49
  [FromQuery] long? statusId,
50
  [FromQuery] long? projectId)
51
  {
52
+ var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId, User.GetRoleName(), User.GetUserId());
53
  return StatusCode(result.StatusCode, result);
54
  }
55
 
 
59
  [FromQuery] long? statusId,
60
  [FromQuery] long? projectId)
61
  {
62
+ var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId, User.GetRoleName(), User.GetUserId());
63
  if (!result.IsSuccess || result.Value == null)
64
  return BadRequest(new { message = result.ErrorMessage });
65
  var bytes = _reportService.ExportTaskStatusSummaryToExcel(result.Value);
 
73
  public async Task<ActionResult<Result<IEnumerable<TeamProductivityReportDto>>>> GetTeamProductivity(
74
  [FromQuery] string? search)
75
  {
76
+ var result = await _reportService.GetTeamProductivityAsync(search, User.GetRoleName(), User.GetUserId());
77
  return StatusCode(result.StatusCode, result);
78
  }
79
 
80
  [HttpGet("team-productivity/excel")]
81
  public async Task<IActionResult> DownloadTeamProductivityExcel([FromQuery] string? search)
82
  {
83
+ var result = await _reportService.GetTeamProductivityAsync(search, User.GetRoleName(), User.GetUserId());
84
  if (!result.IsSuccess || result.Value == null)
85
  return BadRequest(new { message = result.ErrorMessage });
86
  var bytes = _reportService.ExportTeamProductivityToExcel(result.Value);
 
95
  [FromQuery] string? search,
96
  [FromQuery] long? projectId)
97
  {
98
+ var result = await _reportService.GetOverdueCriticalTasksAsync(search, projectId, User.GetRoleName(), User.GetUserId());
99
  return StatusCode(result.StatusCode, result);
100
  }
101
 
 
104
  [FromQuery] string? search,
105
  [FromQuery] long? projectId)
106
  {
107
+ var result = await _reportService.GetOverdueCriticalTasksAsync(search, projectId, User.GetRoleName(), User.GetUserId());
108
  if (!result.IsSuccess || result.Value == null)
109
  return BadRequest(new { message = result.ErrorMessage });
110
  var bytes = _reportService.ExportOverdueCriticalToExcel(result.Value);
 
120
  [FromQuery] long? projectId,
121
  [FromQuery] long? statusId)
122
  {
123
+ var result = await _reportService.GetTimeTrackingReportAsync(search, projectId, statusId, User.GetRoleName(), User.GetUserId());
124
  return StatusCode(result.StatusCode, result);
125
  }
126
  }
TaskTrackingSystem.WebApi/Features/Report/ReportService.cs CHANGED
@@ -22,12 +22,68 @@ namespace TaskTrackingSystem.WebApi.Features.Report
22
  _db = db;
23
  }
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  // ─── Legacy endpoints (kept for backward compatibility) ───────────────────
26
 
27
  public async Task<Result<IEnumerable<TaskReportDto>>> GetTasksReportAsync(
28
- DateTime? startDate, DateTime? endDate, string? status, int? projectId)
29
  {
30
- var query = _db.Tasks
31
  .Include(t => t.Project)
32
  .Include(t => t.AssignedToNavigation)
33
  .Include(t => t.AssignedByNavigation)
@@ -76,19 +132,19 @@ namespace TaskTrackingSystem.WebApi.Features.Report
76
  return Result<IEnumerable<TaskReportDto>>.Success(list);
77
  }
78
 
79
- public async Task<Result<IEnumerable<UserProductivityDto>>> GetUserProductivityReportAsync()
80
  {
81
- var users = await _db.Users.Where(u => !u.IsDeleted).ToListAsync();
 
 
 
82
  var list = new List<UserProductivityDto>();
83
  foreach (var user in users)
84
  {
85
- var tasks = await _db.Tasks
86
- .Include(t => t.TaskHistories)
87
- .Where(t => t.AssignedTo == user.Id && t.IsDeleted != true)
88
- .ToListAsync();
89
 
90
- int total = tasks.Count;
91
- var completedTasks = tasks.Where(t => t.StatusId == 3).ToList();
92
  int done = completedTasks.Count;
93
 
94
  int onTimeCount = completedTasks.Count(t =>
@@ -114,9 +170,9 @@ namespace TaskTrackingSystem.WebApi.Features.Report
114
  // ─── Report 1: Task Status Summary ────────────────────────────────────────
115
 
116
  public async Task<Result<IEnumerable<TaskStatusSummaryDto>>> GetTaskStatusSummaryAsync(
117
- string? search, long? statusId, long? projectId)
118
  {
119
- var query = _db.Tasks
120
  .Include(t => t.Project)
121
  .Include(t => t.AssignedToNavigation)
122
  .Where(t => t.IsDeleted != true);
@@ -189,10 +245,12 @@ namespace TaskTrackingSystem.WebApi.Features.Report
189
 
190
  // ─── Report 2: Team Productivity ──────────────────────────────────────────
191
 
192
- public async Task<Result<IEnumerable<TeamProductivityReportDto>>> GetTeamProductivityAsync(string? search)
193
  {
194
- var users = await _db.Users.Where(u => !u.IsDeleted).ToListAsync();
195
- var allTasks = await _db.Tasks.Where(t => t.IsDeleted != true && t.AssignedTo != null).ToListAsync();
 
 
196
  var now = DateTime.UtcNow;
197
 
198
  var list = users.Select(u =>
@@ -256,10 +314,10 @@ namespace TaskTrackingSystem.WebApi.Features.Report
256
 
257
  // ─── Report 3: Overdue & Critical Tasks ───────────────────────────────────
258
 
259
- public async Task<Result<IEnumerable<OverdueCriticalTaskDto>>> GetOverdueCriticalTasksAsync(string? search, long? projectId)
260
  {
261
  var now = DateTime.UtcNow;
262
- var query = _db.Tasks
263
  .Include(t => t.Project)
264
  .Include(t => t.AssignedToNavigation)
265
  .Where(t => t.IsDeleted != true && t.StatusId != 3 &&
@@ -330,9 +388,9 @@ namespace TaskTrackingSystem.WebApi.Features.Report
330
  }
331
 
332
  public async Task<Result<TimeTrackingReportDto>> GetTimeTrackingReportAsync(
333
- string? search, long? projectId, long? statusId)
334
  {
335
- var query = _db.Tasks
336
  .Include(t => t.Project)
337
  .Include(t => t.AssignedToNavigation)
338
  .Include(t => t.TimeLogs.Where(tl => !tl.IsDeleted))
@@ -463,3 +521,4 @@ namespace TaskTrackingSystem.WebApi.Features.Report
463
  }
464
  }
465
  }
 
 
22
  _db = db;
23
  }
24
 
25
+ private static bool IsAdmin(string roleName)
26
+ {
27
+ return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase);
28
+ }
29
+
30
+ private IQueryable<TaskTrackingSystem.Database.AppDbContextModels.Task> BuildAccessibleTaskQuery(string roleName, long currentUserId)
31
+ {
32
+ var query = _db.Tasks.Where(t => t.IsDeleted != true);
33
+
34
+ if (IsAdmin(roleName))
35
+ {
36
+ return query;
37
+ }
38
+
39
+ if (roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase))
40
+ {
41
+ return query.Where(t =>
42
+ t.AssignedTo == currentUserId ||
43
+ t.CreatedBy == currentUserId ||
44
+ t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId));
45
+ }
46
+
47
+ return query.Where(t =>
48
+ t.AssignedTo == currentUserId ||
49
+ t.CreatedBy == currentUserId);
50
+ }
51
+
52
+ private IQueryable<TaskTrackingSystem.Database.AppDbContextModels.Project> BuildAccessibleProjectQuery(string roleName, long currentUserId)
53
+ {
54
+ var query = _db.Projects.Where(p => p.IsDeleted != true);
55
+
56
+ if (IsAdmin(roleName))
57
+ {
58
+ return query;
59
+ }
60
+
61
+ return query.Where(p =>
62
+ p.CreatedById == currentUserId ||
63
+ p.ProjectMembers.Any(pm => pm.UserId == currentUserId));
64
+ }
65
+
66
+ private IQueryable<TaskTrackingSystem.Database.AppDbContextModels.User> BuildAccessibleUserQuery(string roleName, long currentUserId)
67
+ {
68
+ var users = _db.Users.Where(u => !u.IsDeleted);
69
+
70
+ if (IsAdmin(roleName))
71
+ {
72
+ return users;
73
+ }
74
+
75
+ var accessibleUserIds = BuildAccessibleProjectQuery(roleName, currentUserId)
76
+ .SelectMany(p => p.ProjectMembers.Select(pm => pm.UserId))
77
+ .Distinct();
78
+
79
+ return users.Where(u => u.Id == currentUserId || accessibleUserIds.Contains(u.Id));
80
+ }
81
  // ─── Legacy endpoints (kept for backward compatibility) ───────────────────
82
 
83
  public async Task<Result<IEnumerable<TaskReportDto>>> GetTasksReportAsync(
84
+ DateTime? startDate, DateTime? endDate, string? status, int? projectId, string roleName, long currentUserId)
85
  {
86
+ var query = BuildAccessibleTaskQuery(roleName, currentUserId)
87
  .Include(t => t.Project)
88
  .Include(t => t.AssignedToNavigation)
89
  .Include(t => t.AssignedByNavigation)
 
132
  return Result<IEnumerable<TaskReportDto>>.Success(list);
133
  }
134
 
135
+ public async Task<Result<IEnumerable<UserProductivityDto>>> GetUserProductivityReportAsync(string roleName, long currentUserId)
136
  {
137
+ var users = await BuildAccessibleUserQuery(roleName, currentUserId).ToListAsync();
138
+ var tasks = await BuildAccessibleTaskQuery(roleName, currentUserId)
139
+ .Include(t => t.TaskHistories)
140
+ .ToListAsync();
141
  var list = new List<UserProductivityDto>();
142
  foreach (var user in users)
143
  {
144
+ var userTasks = tasks.Where(t => t.AssignedTo == user.Id).ToList();
 
 
 
145
 
146
+ int total = userTasks.Count;
147
+ var completedTasks = userTasks.Where(t => t.StatusId == 3).ToList();
148
  int done = completedTasks.Count;
149
 
150
  int onTimeCount = completedTasks.Count(t =>
 
170
  // ─── Report 1: Task Status Summary ────────────────────────────────────────
171
 
172
  public async Task<Result<IEnumerable<TaskStatusSummaryDto>>> GetTaskStatusSummaryAsync(
173
+ string? search, long? statusId, long? projectId, string roleName, long currentUserId)
174
  {
175
+ var query = BuildAccessibleTaskQuery(roleName, currentUserId)
176
  .Include(t => t.Project)
177
  .Include(t => t.AssignedToNavigation)
178
  .Where(t => t.IsDeleted != true);
 
245
 
246
  // ─── Report 2: Team Productivity ──────────────────────────────────────────
247
 
248
+ public async Task<Result<IEnumerable<TeamProductivityReportDto>>> GetTeamProductivityAsync(string? search, string roleName, long currentUserId)
249
  {
250
+ var users = await BuildAccessibleUserQuery(roleName, currentUserId).ToListAsync();
251
+ var allTasks = await BuildAccessibleTaskQuery(roleName, currentUserId)
252
+ .Where(t => t.AssignedTo != null)
253
+ .ToListAsync();
254
  var now = DateTime.UtcNow;
255
 
256
  var list = users.Select(u =>
 
314
 
315
  // ─── Report 3: Overdue & Critical Tasks ───────────────────────────────────
316
 
317
+ public async Task<Result<IEnumerable<OverdueCriticalTaskDto>>> GetOverdueCriticalTasksAsync(string? search, long? projectId, string roleName, long currentUserId)
318
  {
319
  var now = DateTime.UtcNow;
320
+ var query = BuildAccessibleTaskQuery(roleName, currentUserId)
321
  .Include(t => t.Project)
322
  .Include(t => t.AssignedToNavigation)
323
  .Where(t => t.IsDeleted != true && t.StatusId != 3 &&
 
388
  }
389
 
390
  public async Task<Result<TimeTrackingReportDto>> GetTimeTrackingReportAsync(
391
+ string? search, long? projectId, long? statusId, string roleName, long currentUserId)
392
  {
393
+ var query = BuildAccessibleTaskQuery(roleName, currentUserId)
394
  .Include(t => t.Project)
395
  .Include(t => t.AssignedToNavigation)
396
  .Include(t => t.TimeLogs.Where(tl => !tl.IsDeleted))
 
521
  }
522
  }
523
  }
524
+
TaskTrackingSystem.WebApi/Features/Task/TaskController.cs CHANGED
@@ -48,8 +48,8 @@ namespace TaskTrackingSystem.WebApi.Features.Task
48
  return Forbid();
49
  }
50
 
51
- long? currentUserId = User.GetUserId();
52
- var result = await _taskService.CreateTaskAsync(createTaskDto, currentUserId);
53
  return StatusCode(result.StatusCode, result);
54
  }
55
 
 
48
  return Forbid();
49
  }
50
 
51
+ var currentUserId = User.GetUserId();
52
+ var result = await _taskService.CreateTaskAsync(createTaskDto, User.GetRoleName(), currentUserId);
53
  return StatusCode(result.StatusCode, result);
54
  }
55
 
TaskTrackingSystem.WebApi/Features/Task/TaskService.cs CHANGED
@@ -75,7 +75,7 @@ namespace TaskTrackingSystem.WebApi.Features.Task
75
  };
76
  }
77
 
78
- public async Task<Result<TaskDto>> CreateTaskAsync(CreateTaskDto dto, long? currentUserId = null)
79
  {
80
  if (string.IsNullOrWhiteSpace(dto.Title))
81
  {
@@ -92,6 +92,16 @@ namespace TaskTrackingSystem.WebApi.Features.Task
92
  return Result<TaskDto>.Failure(ResultMessages.ProjectNotFound(dto.ProjectId), 404);
93
  }
94
 
 
 
 
 
 
 
 
 
 
 
95
  var task = new TaskTrackingSystem.Database.AppDbContextModels.Task
96
  {
97
  Title = dto.Title,
@@ -140,11 +150,16 @@ namespace TaskTrackingSystem.WebApi.Features.Task
140
  var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true);
141
  if (task == null) return Result.Failure(ResultMessages.TaskNotFound(id), 404);
142
 
143
- if (!IsElevated(roleName) && currentUserId.HasValue && !await CanEditTaskAsync(task, currentUserId.Value))
144
  {
145
  return Result.Failure("You do not have access to update this task.", 403);
146
  }
147
 
 
 
 
 
 
148
  task.Title = dto.Title;
149
  task.Description = dto.Description;
150
  task.StatusId = dto.StatusId;
@@ -166,7 +181,7 @@ namespace TaskTrackingSystem.WebApi.Features.Task
166
  var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true);
167
  if (task == null) return Result.Failure(ResultMessages.TaskNotFound(id), 404);
168
 
169
- if (!IsElevated(roleName) && task.CreatedBy != currentUserId && task.AssignedTo != currentUserId)
170
  {
171
  return Result.Failure("You do not have access to delete this task.", 403);
172
  }
@@ -179,19 +194,14 @@ namespace TaskTrackingSystem.WebApi.Features.Task
179
 
180
  public async Task<Result<IEnumerable<TaskDto>>> GetTasksByUserIdAsync(long userId, string roleName, long currentUserId)
181
  {
182
- if (!IsElevated(roleName) && userId != currentUserId)
183
- {
184
- return Result<IEnumerable<TaskDto>>.Failure("You do not have access to view these tasks.", 403);
185
- }
186
-
187
  var userExists = await _db.Users.AnyAsync(u => u.Id == userId && !u.IsDeleted);
188
  if (!userExists)
189
  {
190
  return Result<IEnumerable<TaskDto>>.Failure(ResultMessages.UserNotFound(userId), 404);
191
  }
192
 
193
- var tasks = await _db.Tasks
194
- .Where(t => t.AssignedTo == userId && t.IsDeleted != true)
195
  .Select(t => new TaskDto
196
  {
197
  Id = t.Id,
@@ -224,7 +234,7 @@ namespace TaskTrackingSystem.WebApi.Features.Task
224
  return Result.Failure(ResultMessages.TaskNotFound(id), 404);
225
  }
226
 
227
- if (!IsElevated(roleName) && task.AssignedTo != currentUserId && task.CreatedBy != currentUserId)
228
  {
229
  return Result.Failure("You do not have access to update this task.", 403);
230
  }
@@ -243,30 +253,70 @@ namespace TaskTrackingSystem.WebApi.Features.Task
243
  {
244
  var query = _db.Tasks.Where(t => t.IsDeleted != true);
245
 
246
- if (IsElevated(roleName))
247
  {
248
  return query;
249
  }
250
 
 
 
 
 
 
 
 
 
251
  return query.Where(t =>
252
  t.AssignedTo == currentUserId ||
253
  t.CreatedBy == currentUserId);
254
  }
255
 
256
- private async Task<bool> CanEditTaskAsync(TaskTrackingSystem.Database.AppDbContextModels.Task task, long currentUserId)
257
  {
 
 
 
 
 
258
  if (task.CreatedBy == currentUserId || task.AssignedTo == currentUserId)
259
  {
260
  return true;
261
  }
262
 
263
- return await _db.ProjectMembers.AnyAsync(pm => pm.ProjectId == task.ProjectId && pm.UserId == currentUserId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
 
266
- private static bool IsElevated(string roleName)
267
  {
268
- return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase) ||
269
- roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase);
270
  }
271
  }
272
  }
 
75
  };
76
  }
77
 
78
+ public async Task<Result<TaskDto>> CreateTaskAsync(CreateTaskDto dto, string roleName, long currentUserId)
79
  {
80
  if (string.IsNullOrWhiteSpace(dto.Title))
81
  {
 
92
  return Result<TaskDto>.Failure(ResultMessages.ProjectNotFound(dto.ProjectId), 404);
93
  }
94
 
95
+ if (!await CanAccessProjectAsync(dto.ProjectId, roleName, currentUserId))
96
+ {
97
+ return Result<TaskDto>.Failure("You do not have access to this project.", 403);
98
+ }
99
+
100
+ if (dto.AssignedTo.HasValue && !await IsProjectMemberAsync(dto.ProjectId, dto.AssignedTo.Value))
101
+ {
102
+ return Result<TaskDto>.Failure("Task assignee must belong to the selected project.", 400);
103
+ }
104
+
105
  var task = new TaskTrackingSystem.Database.AppDbContextModels.Task
106
  {
107
  Title = dto.Title,
 
150
  var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true);
151
  if (task == null) return Result.Failure(ResultMessages.TaskNotFound(id), 404);
152
 
153
+ if (!currentUserId.HasValue || !await CanEditTaskAsync(task, roleName, currentUserId.Value))
154
  {
155
  return Result.Failure("You do not have access to update this task.", 403);
156
  }
157
 
158
+ if (dto.AssignedTo.HasValue && !await IsProjectMemberAsync(task.ProjectId, dto.AssignedTo.Value))
159
+ {
160
+ return Result.Failure("Task assignee must belong to the selected project.", 400);
161
+ }
162
+
163
  task.Title = dto.Title;
164
  task.Description = dto.Description;
165
  task.StatusId = dto.StatusId;
 
181
  var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true);
182
  if (task == null) return Result.Failure(ResultMessages.TaskNotFound(id), 404);
183
 
184
+ if (!await CanEditTaskAsync(task, roleName, currentUserId))
185
  {
186
  return Result.Failure("You do not have access to delete this task.", 403);
187
  }
 
194
 
195
  public async Task<Result<IEnumerable<TaskDto>>> GetTasksByUserIdAsync(long userId, string roleName, long currentUserId)
196
  {
 
 
 
 
 
197
  var userExists = await _db.Users.AnyAsync(u => u.Id == userId && !u.IsDeleted);
198
  if (!userExists)
199
  {
200
  return Result<IEnumerable<TaskDto>>.Failure(ResultMessages.UserNotFound(userId), 404);
201
  }
202
 
203
+ var tasks = await BuildAccessibleTaskQuery(roleName, currentUserId)
204
+ .Where(t => t.AssignedTo == userId)
205
  .Select(t => new TaskDto
206
  {
207
  Id = t.Id,
 
234
  return Result.Failure(ResultMessages.TaskNotFound(id), 404);
235
  }
236
 
237
+ if (!await CanEditTaskAsync(task, roleName, currentUserId))
238
  {
239
  return Result.Failure("You do not have access to update this task.", 403);
240
  }
 
253
  {
254
  var query = _db.Tasks.Where(t => t.IsDeleted != true);
255
 
256
+ if (IsAdmin(roleName))
257
  {
258
  return query;
259
  }
260
 
261
+ if (IsManager(roleName))
262
+ {
263
+ return query.Where(t =>
264
+ t.AssignedTo == currentUserId ||
265
+ t.CreatedBy == currentUserId ||
266
+ t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId));
267
+ }
268
+
269
  return query.Where(t =>
270
  t.AssignedTo == currentUserId ||
271
  t.CreatedBy == currentUserId);
272
  }
273
 
274
+ private async Task<bool> CanEditTaskAsync(TaskTrackingSystem.Database.AppDbContextModels.Task task, string roleName, long currentUserId)
275
  {
276
+ if (IsAdmin(roleName))
277
+ {
278
+ return true;
279
+ }
280
+
281
  if (task.CreatedBy == currentUserId || task.AssignedTo == currentUserId)
282
  {
283
  return true;
284
  }
285
 
286
+ if (IsManager(roleName))
287
+ {
288
+ return await _db.ProjectMembers.AnyAsync(pm => pm.ProjectId == task.ProjectId && pm.UserId == currentUserId);
289
+ }
290
+
291
+ return false;
292
+ }
293
+
294
+ private async Task<bool> CanAccessProjectAsync(long projectId, string roleName, long currentUserId)
295
+ {
296
+ if (IsAdmin(roleName))
297
+ {
298
+ return await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
299
+ }
300
+
301
+ return await _db.Projects.AnyAsync(p =>
302
+ p.Id == projectId &&
303
+ p.IsDeleted != true &&
304
+ (p.CreatedById == currentUserId || p.ProjectMembers.Any(pm => pm.UserId == currentUserId)));
305
+ }
306
+
307
+ private async Task<bool> IsProjectMemberAsync(long projectId, long userId)
308
+ {
309
+ return await _db.ProjectMembers.AnyAsync(pm => pm.ProjectId == projectId && pm.UserId == userId);
310
+ }
311
+
312
+ private static bool IsAdmin(string roleName)
313
+ {
314
+ return roleName.Equals("Admin", StringComparison.OrdinalIgnoreCase);
315
  }
316
 
317
+ private static bool IsManager(string roleName)
318
  {
319
+ return roleName.Equals("Manager", StringComparison.OrdinalIgnoreCase);
 
320
  }
321
  }
322
  }
TaskTrackingSystem.WebApp/Components/Pages/ProjectAssign.razor CHANGED
@@ -121,15 +121,24 @@
121
  <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset
122
  </button>
123
  </div>
124
- <div class="w-40 shrink-0">
125
- <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">
126
- <option value="0">All Roles</option>
127
- @foreach (var r in roles)
128
- {
129
- <option value="@r.Id">@r.Name</option>
130
- }
131
- </select>
132
- </div>
 
 
 
 
 
 
 
 
 
133
  <div class="flex items-center gap-3 shrink-0">
134
  <button @onclick="SelectAllUsers" class="text-xs font-semibold text-violet-600 hover:text-violet-800 transition-colors">Select All</button>
135
  <span class="text-slate-300">|</span>
@@ -225,6 +234,8 @@
225
  private List<UserDto> allUsers = new();
226
  private List<RoleDto> roles = new();
227
  private long? adminRoleId;
 
 
228
 
229
  private ProjectDto? SelectedProject { get; set; }
230
  private HashSet<long> SelectedUserIds { get; set; } = new();
@@ -238,6 +249,7 @@
238
  protected override async Task OnInitializedAsync()
239
  {
240
  var authState = await AuthStateProvider.GetAuthenticationStateAsync();
 
241
  canSaveAssignments = authState.User.IsInRole("Admin") || await MenuAuthorization.HasAccessCodeAsync(authState.User, "Projects_Update");
242
  var client = ApiClient.CreateClient();
243
  try
@@ -252,8 +264,14 @@
252
 
253
  var allLoadedRoles = rolesTask.Result ?? new();
254
  var adminRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase));
 
255
  adminRoleId = adminRole?.Id;
 
256
  roles = allLoadedRoles.Where(r => !string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)).ToList();
 
 
 
 
257
  }
258
  catch (Exception ex)
259
  {
@@ -284,10 +302,14 @@
284
  {
285
  result = result.Where(u => u.RoleId != adminRoleId.Value);
286
  }
287
- if (selectedRoleId > 0)
288
  {
289
  result = result.Where(u => u.RoleId == selectedRoleId);
290
  }
 
 
 
 
291
  if (!string.IsNullOrWhiteSpace(userSearch))
292
  {
293
  result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(userSearch, StringComparison.OrdinalIgnoreCase)
@@ -317,7 +339,7 @@
317
  {
318
  userSearchInput = "";
319
  userSearch = "";
320
- selectedRoleId = 0;
321
  }
322
 
323
  private async Task SelectProject(ProjectDto project)
 
121
  <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset
122
  </button>
123
  </div>
124
+ @if (isAdminUser)
125
+ {
126
+ <div class="w-40 shrink-0">
127
+ <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">
128
+ <option value="0">All Roles</option>
129
+ @foreach (var r in roles)
130
+ {
131
+ <option value="@r.Id">@r.Name</option>
132
+ }
133
+ </select>
134
+ </div>
135
+ }
136
+ else
137
+ {
138
+ <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">
139
+ Employees only
140
+ </div>
141
+ }
142
  <div class="flex items-center gap-3 shrink-0">
143
  <button @onclick="SelectAllUsers" class="text-xs font-semibold text-violet-600 hover:text-violet-800 transition-colors">Select All</button>
144
  <span class="text-slate-300">|</span>
 
234
  private List<UserDto> allUsers = new();
235
  private List<RoleDto> roles = new();
236
  private long? adminRoleId;
237
+ private long? employeeRoleId;
238
+ private bool isAdminUser;
239
 
240
  private ProjectDto? SelectedProject { get; set; }
241
  private HashSet<long> SelectedUserIds { get; set; } = new();
 
249
  protected override async Task OnInitializedAsync()
250
  {
251
  var authState = await AuthStateProvider.GetAuthenticationStateAsync();
252
+ isAdminUser = authState.User.IsInRole("Admin");
253
  canSaveAssignments = authState.User.IsInRole("Admin") || await MenuAuthorization.HasAccessCodeAsync(authState.User, "Projects_Update");
254
  var client = ApiClient.CreateClient();
255
  try
 
264
 
265
  var allLoadedRoles = rolesTask.Result ?? new();
266
  var adminRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase));
267
+ var employeeRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Employee", StringComparison.OrdinalIgnoreCase));
268
  adminRoleId = adminRole?.Id;
269
+ employeeRoleId = employeeRole?.Id;
270
  roles = allLoadedRoles.Where(r => !string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)).ToList();
271
+ if (!isAdminUser && employeeRoleId.HasValue)
272
+ {
273
+ selectedRoleId = employeeRoleId.Value;
274
+ }
275
  }
276
  catch (Exception ex)
277
  {
 
302
  {
303
  result = result.Where(u => u.RoleId != adminRoleId.Value);
304
  }
305
+ if (isAdminUser && selectedRoleId > 0)
306
  {
307
  result = result.Where(u => u.RoleId == selectedRoleId);
308
  }
309
+ else if (!isAdminUser && employeeRoleId.HasValue)
310
+ {
311
+ result = result.Where(u => u.RoleId == employeeRoleId.Value);
312
+ }
313
  if (!string.IsNullOrWhiteSpace(userSearch))
314
  {
315
  result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(userSearch, StringComparison.OrdinalIgnoreCase)
 
339
  {
340
  userSearchInput = "";
341
  userSearch = "";
342
+ selectedRoleId = isAdminUser ? 0 : employeeRoleId ?? 0;
343
  }
344
 
345
  private async Task SelectProject(ProjectDto project)
TaskTrackingSystem.WebApp/Components/Pages/TaskAssign.razor CHANGED
@@ -148,11 +148,11 @@
148
  </div>
149
 
150
  <!-- Filter & User Selection -->
151
- <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">
152
- <div class="relative max-w-xs flex-1 flex gap-2">
153
- <div class="relative flex-1">
154
- <input @bind="userSearchInput" type="text" placeholder="Search team members..."
155
- 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" />
156
  <i data-lucide="search" class="absolute left-3 top-2.5 h-3.5 w-3.5 text-slate-400"></i>
157
  </div>
158
  <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">
@@ -162,15 +162,24 @@
162
  <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset
163
  </button>
164
  </div>
165
- <div class="w-40 shrink-0">
166
- <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">
167
- <option value="0">All Roles</option>
168
- @foreach (var r in roles)
169
- {
170
- <option value="@r.Id">@r.Name</option>
171
- }
172
- </select>
173
- </div>
 
 
 
 
 
 
 
 
 
174
  <div class="text-xs font-semibold text-slate-600 shrink-0">
175
  Selected: <span class="text-violet-700 font-bold">@(TargetAssigneeId.HasValue ? allUsers.FirstOrDefault(u => u.Id == TargetAssigneeId)?.Username ?? "Selected" : "Unassigned")</span>
176
  </div>
@@ -258,8 +267,11 @@
258
  private List<TaskDto> tasks = new();
259
  private List<ProjectDto> projects = new();
260
  private List<UserDto> allUsers = new();
 
261
  private List<RoleDto> roles = new();
262
  private long? adminRoleId;
 
 
263
 
264
  private long selectedProjectId = 0;
265
  private long selectedProjectIdInput = 0;
@@ -275,6 +287,7 @@
275
  protected override async Task OnInitializedAsync()
276
  {
277
  var authState = await AuthStateProvider.GetAuthenticationStateAsync();
 
278
  canSaveAssignments = authState.User.IsInRole("Admin") || await MenuAuthorization.HasAccessCodeAsync(authState.User, "Tasks_Update");
279
  var client = ApiClient.CreateClient();
280
  try
@@ -291,8 +304,14 @@
291
 
292
  var allLoadedRoles = rolesTask.Result ?? new();
293
  var adminRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase));
 
294
  adminRoleId = adminRole?.Id;
 
295
  roles = allLoadedRoles.Where(r => !string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)).ToList();
 
 
 
 
296
  }
297
  catch (Exception ex)
298
  {
@@ -331,15 +350,19 @@
331
  {
332
  get
333
  {
334
- var result = allUsers.Where(u => u.IsActive);
335
  if (adminRoleId.HasValue)
336
  {
337
  result = result.Where(u => u.RoleId != adminRoleId.Value);
338
  }
339
- if (selectedRoleId > 0)
340
  {
341
  result = result.Where(u => u.RoleId == selectedRoleId);
342
  }
 
 
 
 
343
  if (!string.IsNullOrWhiteSpace(userSearch))
344
  {
345
  result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(userSearch, StringComparison.OrdinalIgnoreCase)
@@ -364,6 +387,7 @@
364
  selectedProjectId = selectedProjectIdInput;
365
  SelectedTask = null;
366
  TargetAssigneeId = null;
 
367
  statusMessage = null;
368
  }
369
 
@@ -373,6 +397,7 @@
373
  selectedProjectId = 0;
374
  SelectedTask = null;
375
  TargetAssigneeId = null;
 
376
  statusMessage = null;
377
  }
378
 
@@ -389,11 +414,12 @@
389
  selectedRoleId = 0;
390
  }
391
 
392
- private void SelectTask(TaskDto task)
393
  {
394
  SelectedTask = task;
395
  TargetAssigneeId = task.AssignedTo;
396
  statusMessage = null;
 
397
  }
398
 
399
  private void SetTargetAssignee(long? userId)
@@ -419,6 +445,37 @@
419
  await SaveAssignment();
420
  }
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  private async Task SaveAssignment()
423
  {
424
  if (SelectedTask == null) return;
 
148
  </div>
149
 
150
  <!-- Filter & User Selection -->
151
+ <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">
152
+ <div class="relative max-w-xs flex-1 flex gap-2">
153
+ <div class="relative flex-1">
154
+ <input @bind="userSearchInput" type="text" placeholder="Search team members..."
155
+ 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" />
156
  <i data-lucide="search" class="absolute left-3 top-2.5 h-3.5 w-3.5 text-slate-400"></i>
157
  </div>
158
  <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">
 
162
  <i data-lucide="rotate-ccw" class="h-3.5 w-3.5 mr-1.5 text-slate-500"></i> Reset
163
  </button>
164
  </div>
165
+ @if (isAdminUser)
166
+ {
167
+ <div class="w-40 shrink-0">
168
+ <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">
169
+ <option value="0">All Roles</option>
170
+ @foreach (var r in roles)
171
+ {
172
+ <option value="@r.Id">@r.Name</option>
173
+ }
174
+ </select>
175
+ </div>
176
+ }
177
+ else
178
+ {
179
+ <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">
180
+ Employees in this project
181
+ </div>
182
+ }
183
  <div class="text-xs font-semibold text-slate-600 shrink-0">
184
  Selected: <span class="text-violet-700 font-bold">@(TargetAssigneeId.HasValue ? allUsers.FirstOrDefault(u => u.Id == TargetAssigneeId)?.Username ?? "Selected" : "Unassigned")</span>
185
  </div>
 
267
  private List<TaskDto> tasks = new();
268
  private List<ProjectDto> projects = new();
269
  private List<UserDto> allUsers = new();
270
+ private List<UserDto> projectMembers = new();
271
  private List<RoleDto> roles = new();
272
  private long? adminRoleId;
273
+ private long? employeeRoleId;
274
+ private bool isAdminUser;
275
 
276
  private long selectedProjectId = 0;
277
  private long selectedProjectIdInput = 0;
 
287
  protected override async Task OnInitializedAsync()
288
  {
289
  var authState = await AuthStateProvider.GetAuthenticationStateAsync();
290
+ isAdminUser = authState.User.IsInRole("Admin");
291
  canSaveAssignments = authState.User.IsInRole("Admin") || await MenuAuthorization.HasAccessCodeAsync(authState.User, "Tasks_Update");
292
  var client = ApiClient.CreateClient();
293
  try
 
304
 
305
  var allLoadedRoles = rolesTask.Result ?? new();
306
  var adminRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase));
307
+ var employeeRole = allLoadedRoles.FirstOrDefault(r => string.Equals(r.Name, "Employee", StringComparison.OrdinalIgnoreCase));
308
  adminRoleId = adminRole?.Id;
309
+ employeeRoleId = employeeRole?.Id;
310
  roles = allLoadedRoles.Where(r => !string.Equals(r.Name, "Admin", StringComparison.OrdinalIgnoreCase)).ToList();
311
+ if (!isAdminUser && employeeRoleId.HasValue)
312
+ {
313
+ selectedRoleId = employeeRoleId.Value;
314
+ }
315
  }
316
  catch (Exception ex)
317
  {
 
350
  {
351
  get
352
  {
353
+ var result = projectMembers.Where(u => u.IsActive);
354
  if (adminRoleId.HasValue)
355
  {
356
  result = result.Where(u => u.RoleId != adminRoleId.Value);
357
  }
358
+ if (isAdminUser && selectedRoleId > 0)
359
  {
360
  result = result.Where(u => u.RoleId == selectedRoleId);
361
  }
362
+ else if (!isAdminUser && employeeRoleId.HasValue)
363
+ {
364
+ result = result.Where(u => u.RoleId == employeeRoleId.Value);
365
+ }
366
  if (!string.IsNullOrWhiteSpace(userSearch))
367
  {
368
  result = result.Where(u => $"{u.FirstName} {u.LastName}".Contains(userSearch, StringComparison.OrdinalIgnoreCase)
 
387
  selectedProjectId = selectedProjectIdInput;
388
  SelectedTask = null;
389
  TargetAssigneeId = null;
390
+ projectMembers.Clear();
391
  statusMessage = null;
392
  }
393
 
 
397
  selectedProjectId = 0;
398
  SelectedTask = null;
399
  TargetAssigneeId = null;
400
+ projectMembers.Clear();
401
  statusMessage = null;
402
  }
403
 
 
414
  selectedRoleId = 0;
415
  }
416
 
417
+ private async Task SelectTask(TaskDto task)
418
  {
419
  SelectedTask = task;
420
  TargetAssigneeId = task.AssignedTo;
421
  statusMessage = null;
422
+ await LoadProjectMembersAsync(task.ProjectId);
423
  }
424
 
425
  private void SetTargetAssignee(long? userId)
 
445
  await SaveAssignment();
446
  }
447
 
448
+ private async Task LoadProjectMembersAsync(long projectId)
449
+ {
450
+ projectMembers.Clear();
451
+
452
+ if (projectId <= 0)
453
+ {
454
+ return;
455
+ }
456
+
457
+ var client = ApiClient.CreateClient();
458
+ try
459
+ {
460
+ var membersResult = await client.GetFromJsonAsync<Result<IEnumerable<UserDto>>>($"Project/{projectId}/members", Serialization.CaseInsensitive);
461
+ if (membersResult?.IsSuccess == true && membersResult.Value != null)
462
+ {
463
+ projectMembers = membersResult.Value.ToList();
464
+ }
465
+ }
466
+ catch (Exception ex)
467
+ {
468
+ Console.WriteLine($"Error loading project members: {ex.Message}");
469
+ statusMessage = "Failed to load project members.";
470
+ isError = true;
471
+ }
472
+ finally
473
+ {
474
+ await InvokeAsync(StateHasChanged);
475
+ await JS.InvokeVoidAsync("initIcons");
476
+ }
477
+ }
478
+
479
  private async Task SaveAssignment()
480
  {
481
  if (SelectedTask == null) return;