Spaces:
Running
Running
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 +19 -8
- TaskTrackingSystem.WebApi/Features/Project/ProjectController.cs +5 -5
- TaskTrackingSystem.WebApi/Features/Project/ProjectService.cs +28 -9
- TaskTrackingSystem.WebApi/Features/Report/ReportController.cs +10 -9
- TaskTrackingSystem.WebApi/Features/Report/ReportService.cs +78 -19
- TaskTrackingSystem.WebApi/Features/Task/TaskController.cs +2 -2
- TaskTrackingSystem.WebApi/Features/Task/TaskService.cs +67 -17
- TaskTrackingSystem.WebApp/Components/Pages/ProjectAssign.razor +33 -11
- TaskTrackingSystem.WebApp/Components/Pages/TaskAssign.razor +74 -17
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 =
|
| 28 |
? await _db.Users.CountAsync(u => !u.IsDeleted)
|
| 29 |
-
: await _db.Users
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 (
|
| 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 (
|
| 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
|
| 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 |
-
|
| 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,
|
| 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,
|
| 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,
|
| 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 (
|
| 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 (
|
| 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
|
| 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 =
|
| 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
|
|
|
|
|
|
|
|
|
|
| 82 |
var list = new List<UserProductivityDto>();
|
| 83 |
foreach (var user in users)
|
| 84 |
{
|
| 85 |
-
var tasks =
|
| 86 |
-
.Include(t => t.TaskHistories)
|
| 87 |
-
.Where(t => t.AssignedTo == user.Id && t.IsDeleted != true)
|
| 88 |
-
.ToListAsync();
|
| 89 |
|
| 90 |
-
int total =
|
| 91 |
-
var completedTasks =
|
| 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 =
|
| 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
|
| 195 |
-
var allTasks = await
|
|
|
|
|
|
|
| 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 =
|
| 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 =
|
| 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 |
-
|
| 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,
|
| 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 (!
|
| 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 (!
|
| 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
|
| 194 |
-
.Where(t => t.AssignedTo == userId
|
| 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 (!
|
| 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 (
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
}
|
| 265 |
|
| 266 |
-
private static bool
|
| 267 |
{
|
| 268 |
-
return roleName.Equals("
|
| 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 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
@
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 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 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
@
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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
|
| 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;
|