diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java index 9fa71e91..ba517760 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java @@ -24,9 +24,4 @@ public record FilterTeamStatusRequest( taskTitle = taskTitle == null ? "" : taskTitle; } - // 카테고리 유효성 검사 - public boolean isValid() { - // 1차 카테고리가 없으면 2차 카테고리는 선택할 수 없으므로 - return mainCategoryIds.isEmpty() || !categoryIds.isEmpty(); - } } diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TaskItemResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TaskItemResponse.java index 196d2407..279281cf 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TaskItemResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TaskItemResponse.java @@ -2,6 +2,7 @@ import clap.server.adapter.outbound.persistense.entity.task.constant.LabelColor; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; +import clap.server.domain.model.task.Label; import java.time.LocalDateTime; diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamStatusResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamStatusResponse.java index 1cad188c..95e9be15 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamStatusResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamStatusResponse.java @@ -3,9 +3,23 @@ import java.util.List; public record TeamStatusResponse( - List members + List members, + int totalInProgressTaskCount, + int totalPendingTaskCount, + int totalTaskCount ) { + // 기존 생성자 (3개 파라미터) + public TeamStatusResponse(List members, int totalInProgressTaskCount, int totalPendingTaskCount) { + this( + (members == null) ? List.of() : members, + totalInProgressTaskCount, + totalPendingTaskCount, + totalInProgressTaskCount + totalPendingTaskCount + ); + } + + // 추가된 생성자 (List만 받음) public TeamStatusResponse(List members) { - this.members = (members == null) ? List.of() : members; + this(members, 0, 0); // 기본값을 사용하여 생성 } } diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskItemResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskItemResponse.java index 43ae6c43..1b52b0d4 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskItemResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskItemResponse.java @@ -1,5 +1,6 @@ package clap.server.adapter.inbound.web.dto.task.response; +import clap.server.adapter.outbound.persistense.entity.task.constant.LabelColor; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import java.time.LocalDateTime; @@ -10,6 +11,7 @@ public record TeamTaskItemResponse( String title, String mainCategoryName, String categoryName, + LabelInfo labelInfo, String requesterNickname, String requesterImageUrl, String requesterDepartment, @@ -17,4 +19,10 @@ public record TeamTaskItemResponse( TaskStatus taskStatus, LocalDateTime createdAt ) { -} + public static record LabelInfo( + String labelName, + LabelColor labelColor + ) { + } + +} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskResponse.java index 557b77f4..b8a9019c 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/response/TeamTaskResponse.java @@ -1,5 +1,6 @@ package clap.server.adapter.inbound.web.dto.task.response; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import com.querydsl.core.annotations.QueryProjection; import java.util.List; diff --git a/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java b/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java index 69a559bf..41997c09 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java @@ -5,25 +5,25 @@ import clap.server.adapter.inbound.web.dto.task.response.TeamStatusResponse; import clap.server.application.service.task.TeamStatusService; import clap.server.common.annotation.architecture.WebAdapter; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +@Tag(name = "02. Task [담당자]") +@WebAdapter @RestController -@RequestMapping("/api/team-status") @RequiredArgsConstructor -@Tag(name = "팀 현황 조회 API") -@WebAdapter -public class TeamStatusController { - +@RequestMapping("/api/team-status") + public class TeamStatusController { + private final TeamStatusService teamStatusService; - + @Operation(summary = "팀 현황 필터링 조회 API") @GetMapping("/filter") - public ResponseEntity filterTeamStatus(@Valid@ModelAttribute FilterTeamStatusRequest filter) { + public ResponseEntity filterTeamStatus(@Validated @ModelAttribute FilterTeamStatusRequest filter) { TeamStatusResponse response = teamStatusService.filterTeamStatus(filter); return ResponseEntity.ok(response); } -} \ No newline at end of file +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java index 5a9e9a29..48e6af25 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java @@ -3,6 +3,8 @@ import clap.server.adapter.inbound.web.dto.task.request.FilterTaskBoardRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTaskListRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTeamStatusRequest; +import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; +import clap.server.adapter.inbound.web.dto.task.response.TeamTaskItemResponse; import clap.server.adapter.inbound.web.dto.task.response.TeamTaskItemResponse; import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; @@ -56,30 +58,37 @@ public Page findTasksAssignedByManager(Long processorId, Pageable pa @Override public List findTeamStatus(Long memberId, FilterTeamStatusRequest filter) { + // filter가 null인 경우에도 기본적으로 모든 데이터를 조회하도록 처리 BooleanBuilder builder = new BooleanBuilder(); - // 담당자 ID 필터링 - if (memberId != null) { - builder.and(taskEntity.processor.memberId.eq(memberId)); - } + // 필터가 null인 경우, 기본적으로 모든 데이터 조회 + if (filter != null) { + // 진행 중 또는 완료 대기 상태 필터링 + builder.and(taskEntity.taskStatus.in(TaskStatus.IN_PROGRESS, TaskStatus.PENDING_COMPLETED)); - // 작업 타이틀 필터링 - if (filter.taskTitle() != null && !filter.taskTitle().isEmpty()) { - builder.and(taskEntity.title.containsIgnoreCase(filter.taskTitle())); - } + // 담당자 ID 필터링 + if (memberId != null) { + builder.and(taskEntity.processor.memberId.eq(memberId)); + } - // 1차 카테고리 필터링 - if (!filter.mainCategoryIds().isEmpty()) { - builder.and(taskEntity.category.mainCategory.categoryId.in(filter.mainCategoryIds())); - } + // 작업 타이틀 필터링 + if (filter.taskTitle() != null && !filter.taskTitle().isEmpty()) { + builder.and(taskEntity.title.containsIgnoreCase(filter.taskTitle())); + } - // 2차 카테고리 필터링 - if (!filter.categoryIds().isEmpty()) { - builder.and(taskEntity.category.categoryId.in(filter.categoryIds())); + // 1차 카테고리 필터링 (빈 배열인 경우, 필터링하지 않음) + if (filter.mainCategoryIds() != null && !filter.mainCategoryIds().isEmpty()) { + builder.and(taskEntity.category.mainCategory.categoryId.in(filter.mainCategoryIds())); + } + + // 2차 카테고리 필터링 (빈 배열인 경우, 필터링하지 않음) + if (filter.categoryIds() != null && !filter.categoryIds().isEmpty()) { + builder.and(taskEntity.category.categoryId.in(filter.categoryIds())); + } } // 정렬 조건 적용 - OrderSpecifier orderBy = "기여도순".equals(filter.sortBy()) + OrderSpecifier orderBy = "기여도순".equals(filter != null ? filter.sortBy() : "") ? new CaseBuilder() .when(taskEntity.taskStatus.eq(TaskStatus.IN_PROGRESS) .or(taskEntity.taskStatus.eq(TaskStatus.PENDING_COMPLETED))) @@ -106,6 +115,11 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ taskEntity.getTitle(), taskEntity.getCategory().getMainCategory().getName(), taskEntity.getCategory().getName(), + taskEntity.getLabel() != null ? + new TeamTaskItemResponse.LabelInfo( + taskEntity.getLabel().getLabelName(), + taskEntity.getLabel().getLabelColor() + ) : null, taskEntity.getRequester().getNickname(), taskEntity.getRequester().getImageUrl(), taskEntity.getRequester().getDepartment().getName(), @@ -114,6 +128,10 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ taskEntity.getCreatedAt() )).collect(Collectors.toList()); + int inProgressTaskCount = (int) entry.getValue().stream().filter(t -> t.getTaskStatus() == TaskStatus.IN_PROGRESS).count(); + int pendingTaskCount = (int) entry.getValue().stream().filter(t -> t.getTaskStatus() == TaskStatus.PENDING_COMPLETED).count(); + int totalTaskCount = inProgressTaskCount + pendingTaskCount; + return new TeamTaskResponse( entry.getKey(), entry.getValue().get(0).getProcessor().getNickname(), @@ -125,8 +143,11 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ taskResponses ); }).collect(Collectors.toList()); + } + + private boolean isValidTitle(FilterTeamStatusRequest filter) { return filter.taskTitle() != null && !filter.taskTitle().isEmpty(); } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java index fe78f507..c1df73a3 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java @@ -6,7 +6,6 @@ import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import io.lettuce.core.dynamic.annotation.Param; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -54,7 +53,7 @@ Optional findTopByProcessor_MemberIdAndTaskStatusAndProcessorOrderAf Long processorId, TaskStatus taskStatus, Long processorOrder); @Query("SELECT t FROM TaskEntity t JOIN FETCH t.processor p WHERE (:memberId IS NULL OR p.memberId = :memberId) ") - Page findTeamStatus(@Param("memberId") Long memberId, FilterTeamStatusRequest filter, Pageable pageable); + List findTeamStatus(@Param("memberId") Long memberId, FilterTeamStatusRequest filter); Optional findTopByProcessor_MemberIdAndTaskStatusAndTaskIdLessThanOrderByTaskIdDesc(Long processorId, TaskStatus taskStatus, Long taskId); diff --git a/src/main/java/clap/server/application/port/inbound/task/LoadTeamStatusUsecase.java b/src/main/java/clap/server/application/port/inbound/task/LoadTeamStatusUsecase.java index 68b204f4..e7e67f45 100644 --- a/src/main/java/clap/server/application/port/inbound/task/LoadTeamStatusUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/task/LoadTeamStatusUsecase.java @@ -5,6 +5,6 @@ import org.springframework.data.domain.Pageable; public interface LoadTeamStatusUsecase { - TeamStatusResponse getTeamStatus(Long memberId, FilterTeamStatusRequest filter, Pageable pageable); + TeamStatusResponse getTeamStatus(Long memberId, FilterTeamStatusRequest filter); } diff --git a/src/main/java/clap/server/application/service/task/TeamStatusService.java b/src/main/java/clap/server/application/service/task/TeamStatusService.java index 24ad09a6..5b6e5ac8 100644 --- a/src/main/java/clap/server/application/service/task/TeamStatusService.java +++ b/src/main/java/clap/server/application/service/task/TeamStatusService.java @@ -6,7 +6,6 @@ import clap.server.application.port.inbound.task.FilterTeamStatusUsecase; import clap.server.application.port.inbound.task.LoadTeamStatusUsecase; import clap.server.application.port.outbound.task.LoadTaskPort; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; @@ -21,8 +20,8 @@ public TeamStatusService(LoadTaskPort loadTaskPort) { } @Override - public TeamStatusResponse getTeamStatus(Long memberId, FilterTeamStatusRequest filter, Pageable pageable) { - List members = loadTaskPort.findTeamStatus(memberId, filter); // 페이징 처리 + public TeamStatusResponse getTeamStatus(Long memberId, FilterTeamStatusRequest filter) { + List members = loadTaskPort.findTeamStatus(memberId, filter); return new TeamStatusResponse(members); }