diff --git a/Marco.Pms.Model/Utilities/ImageFilter.cs b/Marco.Pms.Model/Utilities/ImageFilter.cs new file mode 100644 index 0000000..a5cb7f7 --- /dev/null +++ b/Marco.Pms.Model/Utilities/ImageFilter.cs @@ -0,0 +1,14 @@ +namespace Marco.Pms.Model.Utilities +{ + public class ImageFilter + { + public List? BuildingIds { get; set; } + public List? FloorIds { get; set; } + public List? WorkAreaIds { get; set; } + public List? WorkCategoryIds { get; set; } + public List? ActivityIds { get; set; } + public List? UploadedByIds { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/ImageController.cs b/Marco.Pms.Services/Controllers/ImageController.cs index 19af70f..eaab3c6 100644 --- a/Marco.Pms.Services/Controllers/ImageController.cs +++ b/Marco.Pms.Services/Controllers/ImageController.cs @@ -1,4 +1,5 @@ -using Marco.Pms.DataAccess.Data; +using System.Text.Json; +using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Activities; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mapper; @@ -35,9 +36,10 @@ namespace Marco.Pms.Services.Controllers } [HttpGet("images/{projectId}")] - public async Task GetImageList(Guid projectId) + + public async Task GetImageList(Guid projectId, [FromQuery] string? filter) { - _logger.LogInfo("GetImageList called for ProjectId: {ProjectId}", projectId); + _logger.LogInfo("[GetImageList] Called by Employee for ProjectId: {ProjectId}", projectId); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); @@ -45,133 +47,178 @@ namespace Marco.Pms.Services.Controllers var isProjectExist = await _context.Projects.AnyAsync(p => p.Id == projectId && p.TenantId == tenantId); if (!isProjectExist) { - _logger.LogWarning("Project not found for ProjectId: {ProjectId}", projectId); + _logger.LogWarning("[GetImageList] ProjectId: {ProjectId} not found", projectId); return BadRequest(ApiResponse.ErrorResponse("Project not found", "Project not found in database", 400)); } - // Step 2: Check permission + // Step 2: Check project access permission var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.ToString()); if (!hasPermission) { - _logger.LogWarning("No access to ProjectId: {ProjectId} for EmployeeId: {EmployeeId}", projectId, loggedInEmployee.Id); + _logger.LogWarning("[GetImageList] Access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); return StatusCode(403, ApiResponse.ErrorResponse("You don't have access", "You don't have access", 403)); } + // Step 3: Deserialize filter + ImageFilter? imageFilter = null; + if (!string.IsNullOrWhiteSpace(filter)) + { + try + { + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + string unescapedJsonString = JsonSerializer.Deserialize(filter, options) ?? ""; + imageFilter = JsonSerializer.Deserialize(unescapedJsonString, options); + } + catch (Exception ex) + { + _logger.LogWarning("[GetImageList] Failed to parse filter: {Message}", ex.Message); + } + } + + // Step 4: Extract filter values + var buildingIds = imageFilter?.BuildingIds; + var floorIds = imageFilter?.FloorIds; + var workAreaIds = imageFilter?.WorkAreaIds; + var activityIds = imageFilter?.ActivityIds; + var workCategoryIds = imageFilter?.WorkCategoryIds; + var startDate = imageFilter?.StartDate; + var endDate = imageFilter?.EndDate; + var uploadedByIds = imageFilter?.UploadedByIds; + + // Step 5: Fetch building > floor > area > work item hierarchy // Step 3: Fetch building > floor > work area > work item hierarchy - var buildings = await _context.Buildings + List? buildings = null; + List? floors = null; + List? workAreas = null; + //List? workItems = null; + //List? documents = null; + + if (buildingIds != null && buildingIds.Count > 0) + { + + buildings = await _context.Buildings + .Where(b => b.ProjectId == projectId && buildingIds.Contains(b.Id)) + .ToListAsync(); + } + else + { + buildings = await _context.Buildings .Where(b => b.ProjectId == projectId) - .Select(b => new { b.Id, b.Name }) .ToListAsync(); - var buildingIds = buildings.Select(b => b.Id).ToList(); + buildingIds = buildings.Select(b => b.Id).ToList(); + } - var floors = await _context.Floor + if (floorIds != null && floorIds.Count > 0) + { + floors = await _context.Floor + .Where(f => buildingIds.Contains(f.BuildingId) && floorIds.Contains(f.Id)) + .ToListAsync(); + } + else + { + floors = await _context.Floor .Where(f => buildingIds.Contains(f.BuildingId)) - .Select(f => new { f.Id, f.BuildingId, f.FloorName }) .ToListAsync(); - var floorIds = floors.Select(f => f.Id).ToList(); - - var workAreas = await _context.WorkAreas + floorIds = floors.Select(f => f.Id).ToList(); + } + if (workAreaIds != null && workAreaIds.Count > 0) + { + workAreas = await _context.WorkAreas + .Where(wa => floorIds.Contains(wa.FloorId) && workAreaIds.Contains(wa.Id)) + .ToListAsync(); + } + else + { + workAreas = await _context.WorkAreas .Where(wa => floorIds.Contains(wa.FloorId)) - .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) .ToListAsync(); - var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); + workAreaIds = workAreas.Select(wa => wa.Id).ToList(); + } - var workItems = await _context.WorkItems - .Include(wi => wi.ActivityMaster) - .Include(wi => wi.WorkCategoryMaster) - .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) - .Select(wi => new { wi.Id, wi.WorkAreaId, wi.ActivityMaster, wi.WorkCategoryMaster }) - .ToListAsync(); + var workItemsQuery = _context.WorkItems.Include(w => w.ActivityMaster).Include(w => w.WorkCategoryMaster) + .Where(wi => workAreaIds.Contains(wi.WorkAreaId)); + if (activityIds?.Any() == true) workItemsQuery = workItemsQuery.Where(wi => activityIds.Contains(wi.ActivityId)); + if (workCategoryIds?.Any() == true) + { + workItemsQuery = workItemsQuery.Where(wi => wi.WorkCategoryMaster != null && workCategoryIds.Contains(wi.WorkCategoryMaster.Id)); + } + var workItems = await workItemsQuery.ToListAsync(); var workItemIds = workItems.Select(wi => wi.Id).ToList(); - // Step 4: Fetch task and comment data - var tasks = await _context.TaskAllocations - .Include(t => t.ReportedBy) - .Where(t => workItemIds.Contains(t.WorkItemId)) - .ToListAsync(); - + // Step 6: Fetch task allocations and comments + var tasks = await _context.TaskAllocations.Include(t => t.ReportedBy) + .Where(t => workItemIds.Contains(t.WorkItemId)).ToListAsync(); var taskIds = tasks.Select(t => t.Id).ToList(); - var comments = await _context.TaskComments - .Include(c => c.Employee) - .Where(c => taskIds.Contains(c.TaskAllocationId)) - .ToListAsync(); - + var comments = await _context.TaskComments.Include(c => c.Employee) + .Where(c => taskIds.Contains(c.TaskAllocationId)).ToListAsync(); var commentIds = comments.Select(c => c.Id).ToList(); - // Step 5: Fetch attachments and related documents var attachments = await _context.TaskAttachments - .Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId)) - .ToListAsync(); + .Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId)).ToListAsync(); var documentIds = attachments.Select(ta => ta.DocumentId).ToList(); - var documents = await _context.Documents - .Include(d => d.UploadedBy) - .Where(d => documentIds.Contains(d.Id)) - .ToListAsync(); + // Step 7: Fetch and filter documents + var docQuery = _context.Documents.Include(d => d.UploadedBy) + .Where(d => documentIds.Contains(d.Id) && d.TenantId == tenantId); + if (startDate != null && endDate != null) + { + docQuery = docQuery.Where(d => d.UploadedAt.Date >= startDate.Value.Date && d.UploadedAt.Date <= endDate.Value.Date); + } + var documents = await docQuery.ToListAsync(); - // Step 6: Prepare view models - var documentVM = documents - .Select(d => + // Step 8: Build response + var documentVM = documents.Select(d => + { + var refId = attachments.FirstOrDefault(ta => ta.DocumentId == d.Id)?.ReferenceId; + var task = tasks.FirstOrDefault(t => t.Id == refId); + var comment = comments.FirstOrDefault(c => c.Id == refId); + + var source = task != null ? "Report" : comment != null ? "Comment" : ""; + var uploadedBy = task?.ReportedBy ?? comment?.Employee; + + var workItem = workItems.FirstOrDefault(w => w.Id == task?.WorkItemId); + var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId); + var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId); + var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); + + return new { - var referenceId = attachments - .Where(ta => ta.DocumentId == d.Id) - .Select(ta => ta.ReferenceId) - .FirstOrDefault(); + Id = d.Id, + BatchId = d.BatchId, + thumbnailUrl = d.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key) : (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null), + ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null, + UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(), + UploadedAt = d.UploadedAt, + Source = source, + ProjectId = projectId, + BuildingId = building?.Id, + BuildingName = building?.Name, + FloorIds = floor?.Id, + FloorName = floor?.FloorName, + WorkAreaId = workArea?.Id, + WorkAreaName = workArea?.AreaName, + TaskId = task?.Id, + ActivityId = workItem?.ActivityMaster?.Id, + ActivityName = workItem?.ActivityMaster?.ActivityName, + WorkCategoryId = workItem?.WorkCategoryMaster?.Id, + WorkCategoryName = workItem?.WorkCategoryMaster?.Name, + CommentId = comment?.Id, + Comment = comment?.Comment + }; + }).ToList(); - var task = tasks.FirstOrDefault(t => t.Id == referenceId); - var comment = comments.FirstOrDefault(c => c.Id == referenceId); + if (uploadedByIds?.Any() == true) + { + documentVM = documentVM.Where(d => uploadedByIds.Contains(d.UploadedBy?.Id ?? Guid.Empty)).ToList(); + } - string source = ""; - Employee? uploadedBy = null; - if (task != null) - { - uploadedBy = task.ReportedBy; - source = "Report"; - } - else if (comment != null) - { - task = tasks.FirstOrDefault(t => t.Id == comment.TaskAllocationId); - uploadedBy = comment.Employee; - source = "Comment"; - } - - var workItem = workItems.FirstOrDefault(wi => wi.Id == task?.WorkItemId); - var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId); - var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId); - var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); - - return new - { - Id = d.Id, - BatchId = d.BatchId, - thumbnailUrl = d.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key) : (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null), - ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null, - UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(), - UploadedAt = d.UploadedAt, - Source = source, - ProjectId = projectId, - BuildingId = building?.Id, - BuildingName = building?.Name, - FloorIds = floor?.Id, - FloorName = floor?.FloorName, - WorkAreaId = workArea?.Id, - WorkAreaName = workArea?.AreaName, - TaskId = task?.Id, - ActivityName = workItem?.ActivityMaster?.ActivityName, - WorkCategoryId = workItem?.WorkCategoryMaster?.Id, - WorkCategoryName = workItem?.WorkCategoryMaster?.Name, - CommentId = comment?.Id, - Comment = comment?.Comment - }; - }).ToList(); - - _logger.LogInfo("Image list fetched for ProjectId: {ProjectId}. Total documents: {Count}", projectId, documentVM.Count); + _logger.LogInfo("[GetImageList] Fetched {Count} documents for ProjectId: {ProjectId}", documentVM.Count, projectId); return Ok(ApiResponse.SuccessResponse(documentVM, $"{documentVM.Count} image records fetched successfully", 200)); } @@ -269,6 +316,7 @@ namespace Marco.Pms.Services.Controllers WorkAreaId = workArea?.Id, WorkAreaName = workArea?.AreaName, TaskId = task?.Id, + ActivityId = workItem?.ActivityMaster?.Id, ActivityName = workItem?.ActivityMaster?.ActivityName, WorkCategoryId = workItem?.WorkCategoryMaster?.Id, WorkCategoryName = workItem?.WorkCategoryMaster?.Name,