mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 04:49:11 +00:00
Feat: 대시보드 조회 API 실제 데이터 연동
This commit is contained in:
+60
-74
@@ -1,7 +1,10 @@
|
||||
package com.unicorn.hgzero.meeting.infra.controller;
|
||||
|
||||
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Dashboard;
|
||||
import com.unicorn.hgzero.meeting.biz.usecase.in.dashboard.GetDashboardUseCase;
|
||||
import com.unicorn.hgzero.meeting.infra.dto.response.DashboardResponse;
|
||||
import com.unicorn.hgzero.meeting.infra.mapper.DashboardResponseMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
@@ -9,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -28,8 +30,11 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class DashboardController {
|
||||
|
||||
private final GetDashboardUseCase getDashboardUseCase;
|
||||
private final DashboardResponseMapper dashboardResponseMapper;
|
||||
|
||||
/**
|
||||
* 대시보드 데이터 조회 (목 데이터)
|
||||
* 대시보드 데이터 조회
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 대시보드 데이터
|
||||
@@ -50,80 +55,61 @@ public class DashboardController {
|
||||
|
||||
log.info("대시보드 데이터 조회 요청 - userId: {}", userId);
|
||||
|
||||
// 목 데이터 생성
|
||||
DashboardResponse mockResponse = createMockDashboardData();
|
||||
try {
|
||||
// 실제 데이터 조회
|
||||
Dashboard dashboard = getDashboardUseCase.getDashboard(userId);
|
||||
|
||||
// 도메인 객체를 응답 DTO로 변환
|
||||
DashboardResponse response = dashboardResponseMapper.toResponse(dashboard);
|
||||
|
||||
log.info("대시보드 데이터 조회 완료 - userId: {}", userId);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
} catch (Exception e) {
|
||||
log.error("대시보드 데이터 조회 실패 - userId: {}", userId, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 대시보드 데이터 조회
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @param period 조회 기간 (1day, 3days, 7days, 30days, 90days)
|
||||
* @return 기간별 대시보드 데이터
|
||||
*/
|
||||
@Operation(
|
||||
summary = "기간별 대시보드 데이터 조회",
|
||||
description = "사용자별 맞춤 대시보드 정보를 기간 필터로 조회합니다.",
|
||||
security = @SecurityRequirement(name = "bearerAuth")
|
||||
)
|
||||
@GetMapping("/period/{period}")
|
||||
public ResponseEntity<ApiResponse<DashboardResponse>> getDashboardByPeriod(
|
||||
@Parameter(description = "사용자 ID", required = true)
|
||||
@RequestHeader("X-User-Id") String userId,
|
||||
@Parameter(description = "사용자명", required = true)
|
||||
@RequestHeader("X-User-Name") String userName,
|
||||
@Parameter(description = "사용자 이메일", required = true)
|
||||
@RequestHeader("X-User-Email") String userEmail,
|
||||
@Parameter(description = "조회 기간", required = true)
|
||||
@PathVariable String period) {
|
||||
|
||||
log.info("대시보드 데이터 조회 완료 - userId: {}", userId);
|
||||
log.info("기간별 대시보드 데이터 조회 요청 - userId: {}, period: {}", userId, period);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(mockResponse));
|
||||
try {
|
||||
// 실제 데이터 조회
|
||||
Dashboard dashboard = getDashboardUseCase.getDashboardByPeriod(userId, period);
|
||||
|
||||
// 도메인 객체를 응답 DTO로 변환
|
||||
DashboardResponse response = dashboardResponseMapper.toResponse(dashboard);
|
||||
|
||||
log.info("기간별 대시보드 데이터 조회 완료 - userId: {}, period: {}", userId, period);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
} catch (Exception e) {
|
||||
log.error("기간별 대시보드 데이터 조회 실패 - userId: {}, period: {}", userId, period, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 목 데이터 생성
|
||||
*/
|
||||
private DashboardResponse createMockDashboardData() {
|
||||
// 예정된 회의 목 데이터
|
||||
List<DashboardResponse.UpcomingMeetingResponse> upcomingMeetings = Arrays.asList(
|
||||
DashboardResponse.UpcomingMeetingResponse.builder()
|
||||
.meetingId("550e8400-e29b-41d4-a716-446655440001")
|
||||
.title("Q1 전략 회의")
|
||||
.startTime(LocalDateTime.now().plusDays(2).withHour(14).withMinute(0))
|
||||
.endTime(LocalDateTime.now().plusDays(2).withHour(16).withMinute(0))
|
||||
.location("회의실 A")
|
||||
.participantCount(5)
|
||||
.status("SCHEDULED")
|
||||
.build(),
|
||||
DashboardResponse.UpcomingMeetingResponse.builder()
|
||||
.meetingId("550e8400-e29b-41d4-a716-446655440002")
|
||||
.title("개발팀 스프린트 계획")
|
||||
.startTime(LocalDateTime.now().plusDays(3).withHour(10).withMinute(0))
|
||||
.endTime(LocalDateTime.now().plusDays(3).withHour(12).withMinute(0))
|
||||
.location("회의실 B")
|
||||
.participantCount(8)
|
||||
.status("SCHEDULED")
|
||||
.build()
|
||||
);
|
||||
|
||||
// 최근 회의록 목 데이터
|
||||
List<DashboardResponse.RecentMinutesResponse> recentMinutes = Arrays.asList(
|
||||
DashboardResponse.RecentMinutesResponse.builder()
|
||||
.minutesId("770e8400-e29b-41d4-a716-446655440001")
|
||||
.title("아키텍처 설계 회의")
|
||||
.meetingDate(LocalDateTime.now().minusDays(1).withHour(14).withMinute(0))
|
||||
.status("FINALIZED")
|
||||
.participantCount(6)
|
||||
.lastModified(LocalDateTime.now().minusDays(1).withHour(16).withMinute(30))
|
||||
.build(),
|
||||
DashboardResponse.RecentMinutesResponse.builder()
|
||||
.minutesId("770e8400-e29b-41d4-a716-446655440002")
|
||||
.title("UI/UX 검토 회의")
|
||||
.meetingDate(LocalDateTime.now().minusDays(3).withHour(11).withMinute(0))
|
||||
.status("FINALIZED")
|
||||
.participantCount(4)
|
||||
.lastModified(LocalDateTime.now().minusDays(3).withHour(12).withMinute(45))
|
||||
.build(),
|
||||
DashboardResponse.RecentMinutesResponse.builder()
|
||||
.minutesId("770e8400-e29b-41d4-a716-446655440003")
|
||||
.title("API 설계 검토")
|
||||
.meetingDate(LocalDateTime.now().minusDays(5).withHour(15).withMinute(0))
|
||||
.status("DRAFT")
|
||||
.participantCount(3)
|
||||
.lastModified(LocalDateTime.now().minusDays(5).withHour(16).withMinute(15))
|
||||
.build()
|
||||
);
|
||||
|
||||
// 통계 정보 목 데이터
|
||||
DashboardResponse.StatisticsResponse statistics = DashboardResponse.StatisticsResponse.builder()
|
||||
.upcomingMeetingsCount(2)
|
||||
.activeTodosCount(0) // activeTodos 제거로 0으로 설정
|
||||
.todoCompletionRate(0.0) // activeTodos 제거로 0으로 설정
|
||||
.build();
|
||||
|
||||
return DashboardResponse.builder()
|
||||
.upcomingMeetings(upcomingMeetings)
|
||||
.activeTodos(Collections.emptyList()) // activeTodos 빈 리스트로 설정
|
||||
.myMinutes(recentMinutes)
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
-41
@@ -19,8 +19,6 @@ public class DashboardResponse {
|
||||
@Schema(description = "예정된 회의 목록")
|
||||
private final List<UpcomingMeetingResponse> upcomingMeetings;
|
||||
|
||||
@Schema(description = "진행 중 Todo 목록")
|
||||
private final List<ActiveTodoResponse> activeTodos;
|
||||
|
||||
@Schema(description = "최근 회의록 목록")
|
||||
private final List<RecentMinutesResponse> myMinutes;
|
||||
@@ -36,9 +34,6 @@ public class DashboardResponse {
|
||||
.upcomingMeetings(dto.getUpcomingMeetings().stream()
|
||||
.map(UpcomingMeetingResponse::from)
|
||||
.toList())
|
||||
.activeTodos(dto.getActiveTodos().stream()
|
||||
.map(ActiveTodoResponse::from)
|
||||
.toList())
|
||||
.myMinutes(dto.getMyMinutes().stream()
|
||||
.map(RecentMinutesResponse::from)
|
||||
.toList())
|
||||
@@ -84,39 +79,6 @@ public class DashboardResponse {
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@Schema(description = "진행 중 Todo 정보")
|
||||
public static class ActiveTodoResponse {
|
||||
@Schema(description = "Todo ID", example = "660e8400-e29b-41d4-a716-446655440000")
|
||||
private final String todoId;
|
||||
|
||||
@Schema(description = "Todo 내용", example = "API 설계 문서 작성")
|
||||
private final String content;
|
||||
|
||||
@Schema(description = "마감일", example = "2025-01-30")
|
||||
private final String dueDate;
|
||||
|
||||
@Schema(description = "우선순위", example = "HIGH")
|
||||
private final String priority;
|
||||
|
||||
@Schema(description = "Todo 상태", example = "IN_PROGRESS")
|
||||
private final String status;
|
||||
|
||||
@Schema(description = "회의록 ID", example = "770e8400-e29b-41d4-a716-446655440000")
|
||||
private final String minutesId;
|
||||
|
||||
public static ActiveTodoResponse from(DashboardDTO.ActiveTodoDTO dto) {
|
||||
return ActiveTodoResponse.builder()
|
||||
.todoId(dto.getTodoId())
|
||||
.content(dto.getContent())
|
||||
.dueDate(dto.getDueDate())
|
||||
.priority(dto.getPriority())
|
||||
.status(dto.getStatus())
|
||||
.minutesId(dto.getMinutesId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@@ -159,8 +121,6 @@ public class DashboardResponse {
|
||||
@Schema(description = "예정된 회의 수", example = "2")
|
||||
private final Integer upcomingMeetingsCount;
|
||||
|
||||
@Schema(description = "진행 중 Todo 수", example = "5")
|
||||
private final Integer activeTodosCount;
|
||||
|
||||
@Schema(description = "Todo 완료율", example = "68.5")
|
||||
private final Double todoCompletionRate;
|
||||
@@ -168,7 +128,6 @@ public class DashboardResponse {
|
||||
public static StatisticsResponse from(DashboardDTO.StatisticsDTO dto) {
|
||||
return StatisticsResponse.builder()
|
||||
.upcomingMeetingsCount(dto.getUpcomingMeetingsCount())
|
||||
.activeTodosCount(dto.getActiveTodosCount())
|
||||
.todoCompletionRate(dto.getTodoCompletionRate())
|
||||
.build();
|
||||
}
|
||||
|
||||
+223
-93
@@ -1,8 +1,14 @@
|
||||
package com.unicorn.hgzero.meeting.infra.gateway;
|
||||
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Dashboard;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Meeting;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Minutes;
|
||||
import com.unicorn.hgzero.meeting.biz.usecase.out.DashboardReader;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.entity.MeetingEntity;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.entity.MinutesEntity;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.entity.TodoEntity;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.repository.MeetingJpaRepository;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.repository.MeetingParticipantJpaRepository;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.repository.MinutesJpaRepository;
|
||||
import com.unicorn.hgzero.meeting.infra.gateway.repository.TodoJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -11,6 +17,11 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 대시보드 Gateway 구현체
|
||||
@@ -22,106 +33,227 @@ import java.time.LocalDateTime;
|
||||
public class DashboardGateway implements DashboardReader {
|
||||
|
||||
private final MeetingJpaRepository meetingJpaRepository;
|
||||
private final MeetingParticipantJpaRepository meetingParticipantJpaRepository;
|
||||
private final MinutesJpaRepository minutesJpaRepository;
|
||||
private final TodoJpaRepository todoJpaRepository;
|
||||
|
||||
@Override
|
||||
public Dashboard getDashboardByUserId(String userId) {
|
||||
log.debug("Getting dashboard for user: {}", userId);
|
||||
log.info("대시보드 데이터 조회 시작 - userId: {}", userId);
|
||||
|
||||
// 회의 통계 조회
|
||||
long totalMeetings = meetingJpaRepository.findByOrganizerId(userId).size();
|
||||
long scheduledMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "SCHEDULED").size();
|
||||
long inProgressMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "IN_PROGRESS").size();
|
||||
long completedMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "COMPLETED").size();
|
||||
// 1. 다가오는 회의 목록 조회 (향후 30일, 최대 10개)
|
||||
List<Meeting> upcomingMeetings = getUpcomingMeetings(userId);
|
||||
|
||||
// 2. 최근 회의록 목록 조회 (최근 7일, 최대 10개)
|
||||
List<Minutes> recentMinutes = getRecentMinutes(userId);
|
||||
|
||||
// 3. 통계 정보 계산 (최근 30일 기준)
|
||||
Dashboard.Statistics statistics = calculateStatistics(userId);
|
||||
|
||||
// 회의록 통계 조회
|
||||
long totalMinutes = minutesJpaRepository.findByCreatedBy(userId).size();
|
||||
long draftMinutes = minutesJpaRepository.findByCreatedBy(userId).stream()
|
||||
.filter(m -> "DRAFT".equals(m.getStatus()))
|
||||
.count();
|
||||
long finalizedMinutes = minutesJpaRepository.findByCreatedBy(userId).stream()
|
||||
.filter(m -> "FINALIZED".equals(m.getStatus()))
|
||||
.count();
|
||||
|
||||
// Todo 통계 조회
|
||||
long totalTodos = todoJpaRepository.findByAssigneeId(userId).size();
|
||||
long pendingTodos = todoJpaRepository.findByAssigneeIdAndStatus(userId, "PENDING").size();
|
||||
long completedTodos = todoJpaRepository.findByAssigneeIdAndStatus(userId, "COMPLETED").size();
|
||||
long overdueTodos = todoJpaRepository.findByAssigneeId(userId).stream()
|
||||
.filter(todo -> todo.getDueDate() != null
|
||||
&& LocalDate.now().isAfter(todo.getDueDate())
|
||||
&& !"COMPLETED".equals(todo.getStatus()))
|
||||
.count();
|
||||
|
||||
// 통계 객체 생성
|
||||
Dashboard.Statistics statistics = Dashboard.Statistics.builder()
|
||||
.totalMeetings((int) totalMeetings)
|
||||
.scheduledMeetings((int) scheduledMeetings)
|
||||
.inProgressMeetings((int) inProgressMeetings)
|
||||
.completedMeetings((int) completedMeetings)
|
||||
.totalMinutes((int) totalMinutes)
|
||||
.draftMinutes((int) draftMinutes)
|
||||
.finalizedMinutes((int) finalizedMinutes)
|
||||
.totalTodos((int) totalTodos)
|
||||
.pendingTodos((int) pendingTodos)
|
||||
.completedTodos((int) completedTodos)
|
||||
.overdueTodos((int) overdueTodos)
|
||||
.build();
|
||||
|
||||
// 대시보드 생성
|
||||
return Dashboard.builder()
|
||||
Dashboard dashboard = Dashboard.builder()
|
||||
.userId(userId)
|
||||
.period("7days")
|
||||
.upcomingMeetings(upcomingMeetings)
|
||||
.recentMinutes(recentMinutes)
|
||||
.assignedTodos(new ArrayList<>())
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
|
||||
log.info("대시보드 데이터 조회 완료 - userId: {}, 예정 회의: {}개, 최근 회의록: {}개",
|
||||
userId, upcomingMeetings.size(), recentMinutes.size());
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dashboard getDashboardByUserIdAndPeriod(String userId, String period) {
|
||||
log.debug("Getting dashboard for user: {} with period: {}", userId, period);
|
||||
log.info("기간별 대시보드 데이터 조회 시작 - userId: {}, period: {}", userId, period);
|
||||
|
||||
// 기간 계산
|
||||
// 기간에 따른 조회 범위 계산
|
||||
LocalDateTime startTime = calculateStartTime(period);
|
||||
LocalDateTime endTime = LocalDateTime.now();
|
||||
|
||||
// 기간 내 회의 통계 조회
|
||||
long totalMeetings = meetingJpaRepository.findByOrganizerId(userId).stream()
|
||||
.filter(m -> m.getScheduledAt().isAfter(startTime) && m.getScheduledAt().isBefore(endTime))
|
||||
// 1. 기간 내 다가오는 회의 목록 조회
|
||||
List<Meeting> upcomingMeetings = getUpcomingMeetingsByPeriod(userId, startTime, endTime);
|
||||
|
||||
// 2. 기간 내 최근 회의록 목록 조회
|
||||
List<Minutes> recentMinutes = getRecentMinutesByPeriod(userId, startTime, endTime);
|
||||
|
||||
// 3. 기간별 통계 정보 계산
|
||||
Dashboard.Statistics statistics = calculateStatisticsByPeriod(userId, startTime, endTime);
|
||||
|
||||
Dashboard dashboard = Dashboard.builder()
|
||||
.userId(userId)
|
||||
.period(period)
|
||||
.upcomingMeetings(upcomingMeetings)
|
||||
.recentMinutes(recentMinutes)
|
||||
.assignedTodos(new ArrayList<>())
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
|
||||
log.info("기간별 대시보드 데이터 조회 완료 - userId: {}, period: {}", userId, period);
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* 다가오는 회의 목록 조회
|
||||
*/
|
||||
private List<Meeting> getUpcomingMeetings(String userId) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endTime = now.plusDays(30); // 향후 30일
|
||||
|
||||
return getUpcomingMeetingsByPeriod(userId, now, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 다가오는 회의 목록 조회
|
||||
*/
|
||||
private List<Meeting> getUpcomingMeetingsByPeriod(String userId, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
Set<String> userMeetingIds = new HashSet<>();
|
||||
|
||||
// 주최자로 참여하는 예정/진행중 회의 조회
|
||||
List<MeetingEntity> organizerMeetings = meetingJpaRepository.findByScheduledAtBetween(startTime, endTime).stream()
|
||||
.filter(m -> userId.equals(m.getOrganizerId()))
|
||||
.filter(m -> "SCHEDULED".equals(m.getStatus()) || "IN_PROGRESS".equals(m.getStatus()))
|
||||
.toList();
|
||||
|
||||
organizerMeetings.forEach(m -> userMeetingIds.add(m.getMeetingId()));
|
||||
|
||||
// 참석자로 참여하는 예정/진행중 회의 조회
|
||||
List<String> participantMeetingIds = meetingParticipantJpaRepository.findByUserId(userId).stream()
|
||||
.map(p -> p.getMeetingId())
|
||||
.toList();
|
||||
|
||||
List<MeetingEntity> participantMeetings = meetingJpaRepository.findByScheduledAtBetween(startTime, endTime).stream()
|
||||
.filter(m -> participantMeetingIds.contains(m.getMeetingId()))
|
||||
.filter(m -> "SCHEDULED".equals(m.getStatus()) || "IN_PROGRESS".equals(m.getStatus()))
|
||||
.toList();
|
||||
|
||||
participantMeetings.forEach(m -> userMeetingIds.add(m.getMeetingId()));
|
||||
|
||||
// 중복 제거된 회의 목록을 시간순 정렬하여 최대 10개만 반환
|
||||
return meetingJpaRepository.findByScheduledAtBetween(startTime, endTime).stream()
|
||||
.filter(m -> userMeetingIds.contains(m.getMeetingId()))
|
||||
.filter(m -> "SCHEDULED".equals(m.getStatus()) || "IN_PROGRESS".equals(m.getStatus()))
|
||||
.sorted((m1, m2) -> m1.getScheduledAt().compareTo(m2.getScheduledAt()))
|
||||
.limit(10)
|
||||
.map(MeetingEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근 회의록 목록 조회
|
||||
*/
|
||||
private List<Minutes> getRecentMinutes(String userId) {
|
||||
LocalDateTime startTime = LocalDateTime.now().minusDays(7);
|
||||
return getRecentMinutesByPeriod(userId, startTime, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 최근 회의록 목록 조회
|
||||
*/
|
||||
private List<Minutes> getRecentMinutesByPeriod(String userId, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
Set<String> userMinutesIds = new HashSet<>();
|
||||
|
||||
// 작성자로 참여한 회의록 조회
|
||||
List<MinutesEntity> createdMinutes = minutesJpaRepository.findByCreatedBy(userId).stream()
|
||||
.filter(m -> m.getCreatedAt().isAfter(startTime) && m.getCreatedAt().isBefore(endTime))
|
||||
.toList();
|
||||
|
||||
createdMinutes.forEach(m -> userMinutesIds.add(m.getMinutesId()));
|
||||
|
||||
// 참석한 회의의 회의록 조회
|
||||
List<String> participantMeetingIds = meetingParticipantJpaRepository.findByUserId(userId).stream()
|
||||
.map(p -> p.getMeetingId())
|
||||
.toList();
|
||||
|
||||
List<MinutesEntity> participatedMinutes = minutesJpaRepository.findAll().stream()
|
||||
.filter(m -> participantMeetingIds.contains(m.getMeetingId()))
|
||||
.filter(m -> m.getCreatedAt().isAfter(startTime) && m.getCreatedAt().isBefore(endTime))
|
||||
.toList();
|
||||
|
||||
participatedMinutes.forEach(m -> userMinutesIds.add(m.getMinutesId()));
|
||||
|
||||
// 중복 제거 후 최종 수정 시간순 정렬하여 최대 10개만 반환
|
||||
return minutesJpaRepository.findAll().stream()
|
||||
.filter(m -> userMinutesIds.contains(m.getMinutesId()))
|
||||
.sorted((m1, m2) -> {
|
||||
LocalDateTime time1 = m1.getUpdatedAt() != null ? m1.getUpdatedAt() : m1.getCreatedAt();
|
||||
LocalDateTime time2 = m2.getUpdatedAt() != null ? m2.getUpdatedAt() : m2.getCreatedAt();
|
||||
return time2.compareTo(time1); // 최신순
|
||||
})
|
||||
.limit(10)
|
||||
.map(MinutesEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 통계 정보 계산
|
||||
*/
|
||||
private Dashboard.Statistics calculateStatistics(String userId) {
|
||||
LocalDateTime startTime = LocalDateTime.now().minusDays(30); // 최근 30일
|
||||
LocalDateTime endTime = LocalDateTime.now();
|
||||
|
||||
return calculateStatisticsByPeriod(userId, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 통계 정보 계산
|
||||
*/
|
||||
private Dashboard.Statistics calculateStatisticsByPeriod(String userId, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
// 사용자가 관련된 모든 회의 ID 수집
|
||||
Set<String> userMeetingIds = new HashSet<>();
|
||||
|
||||
// 주최자로 참여한 회의
|
||||
meetingJpaRepository.findByOrganizerId(userId).forEach(m -> userMeetingIds.add(m.getMeetingId()));
|
||||
|
||||
// 참석자로 참여한 회의
|
||||
meetingParticipantJpaRepository.findByUserId(userId).stream()
|
||||
.map(p -> p.getMeetingId())
|
||||
.forEach(userMeetingIds::add);
|
||||
|
||||
// 기간 내 회의 통계
|
||||
List<MeetingEntity> periodMeetings = meetingJpaRepository.findByScheduledAtBetween(startTime, endTime).stream()
|
||||
.filter(m -> userMeetingIds.contains(m.getMeetingId()))
|
||||
.toList();
|
||||
|
||||
long totalMeetings = periodMeetings.size();
|
||||
long scheduledMeetings = periodMeetings.stream().filter(m -> "SCHEDULED".equals(m.getStatus())).count();
|
||||
long inProgressMeetings = periodMeetings.stream().filter(m -> "IN_PROGRESS".equals(m.getStatus())).count();
|
||||
long completedMeetings = periodMeetings.stream().filter(m -> "COMPLETED".equals(m.getStatus())).count();
|
||||
|
||||
// 회의록 통계 (사용자가 관련된 모든 회의록)
|
||||
Set<String> userMinutesIds = new HashSet<>();
|
||||
|
||||
// 작성자로 참여한 회의록
|
||||
minutesJpaRepository.findByCreatedBy(userId).forEach(m -> userMinutesIds.add(m.getMinutesId()));
|
||||
|
||||
// 참석한 회의의 회의록
|
||||
userMeetingIds.forEach(meetingId -> {
|
||||
minutesJpaRepository.findByMeetingId(meetingId).forEach(m -> userMinutesIds.add(m.getMinutesId()));
|
||||
});
|
||||
|
||||
List<MinutesEntity> userMinutes = minutesJpaRepository.findAll().stream()
|
||||
.filter(m -> userMinutesIds.contains(m.getMinutesId()))
|
||||
.toList();
|
||||
|
||||
long totalMinutes = userMinutes.size();
|
||||
long draftMinutes = userMinutes.stream().filter(m -> "DRAFT".equals(m.getStatus())).count();
|
||||
long finalizedMinutes = userMinutes.stream().filter(m -> "FINALIZED".equals(m.getStatus())).count();
|
||||
|
||||
// Todo 통계
|
||||
List<TodoEntity> userTodos = todoJpaRepository.findByAssigneeId(userId);
|
||||
long totalTodos = userTodos.size();
|
||||
long pendingTodos = userTodos.stream().filter(t -> "PENDING".equals(t.getStatus())).count();
|
||||
long completedTodos = userTodos.stream().filter(t -> "COMPLETED".equals(t.getStatus())).count();
|
||||
long overdueTodos = userTodos.stream()
|
||||
.filter(t -> t.getDueDate() != null
|
||||
&& LocalDate.now().isAfter(t.getDueDate())
|
||||
&& !"COMPLETED".equals(t.getStatus()))
|
||||
.count();
|
||||
|
||||
long scheduledMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "SCHEDULED").stream()
|
||||
.filter(m -> m.getScheduledAt().isAfter(startTime) && m.getScheduledAt().isBefore(endTime))
|
||||
.count();
|
||||
|
||||
long inProgressMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "IN_PROGRESS").stream()
|
||||
.filter(m -> m.getScheduledAt().isAfter(startTime) && m.getScheduledAt().isBefore(endTime))
|
||||
.count();
|
||||
|
||||
long completedMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "COMPLETED").stream()
|
||||
.filter(m -> m.getScheduledAt().isAfter(startTime) && m.getScheduledAt().isBefore(endTime))
|
||||
.count();
|
||||
|
||||
// 회의록 통계 조회 (전체 기간)
|
||||
long totalMinutes = minutesJpaRepository.findByCreatedBy(userId).size();
|
||||
long draftMinutes = minutesJpaRepository.findByCreatedBy(userId).stream()
|
||||
.filter(m -> "DRAFT".equals(m.getStatus()))
|
||||
.count();
|
||||
long finalizedMinutes = minutesJpaRepository.findByCreatedBy(userId).stream()
|
||||
.filter(m -> "FINALIZED".equals(m.getStatus()))
|
||||
.count();
|
||||
|
||||
// Todo 통계 조회 (전체 기간)
|
||||
long totalTodos = todoJpaRepository.findByAssigneeId(userId).size();
|
||||
long pendingTodos = todoJpaRepository.findByAssigneeIdAndStatus(userId, "PENDING").size();
|
||||
long completedTodos = todoJpaRepository.findByAssigneeIdAndStatus(userId, "COMPLETED").size();
|
||||
long overdueTodos = todoJpaRepository.findByAssigneeId(userId).stream()
|
||||
.filter(todo -> todo.getDueDate() != null
|
||||
&& LocalDate.now().isAfter(todo.getDueDate())
|
||||
&& !"COMPLETED".equals(todo.getStatus()))
|
||||
.count();
|
||||
|
||||
// 통계 객체 생성
|
||||
Dashboard.Statistics statistics = Dashboard.Statistics.builder()
|
||||
return Dashboard.Statistics.builder()
|
||||
.totalMeetings((int) totalMeetings)
|
||||
.scheduledMeetings((int) scheduledMeetings)
|
||||
.inProgressMeetings((int) inProgressMeetings)
|
||||
@@ -134,13 +266,6 @@ public class DashboardGateway implements DashboardReader {
|
||||
.completedTodos((int) completedTodos)
|
||||
.overdueTodos((int) overdueTodos)
|
||||
.build();
|
||||
|
||||
// 대시보드 생성
|
||||
return Dashboard.builder()
|
||||
.userId(userId)
|
||||
.period(period)
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,12 +274,17 @@ public class DashboardGateway implements DashboardReader {
|
||||
private LocalDateTime calculateStartTime(String period) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
return switch (period.toUpperCase()) {
|
||||
case "WEEK" -> now.minusWeeks(1);
|
||||
case "MONTH" -> now.minusMonths(1);
|
||||
case "QUARTER" -> now.minusMonths(3);
|
||||
case "YEAR" -> now.minusYears(1);
|
||||
default -> now.minusMonths(1); // 기본값: 1개월
|
||||
return switch (period.toLowerCase()) {
|
||||
case "1day" -> now.minusDays(1);
|
||||
case "3days" -> now.minusDays(3);
|
||||
case "7days" -> now.minusDays(7);
|
||||
case "30days" -> now.minusDays(30);
|
||||
case "90days" -> now.minusDays(90);
|
||||
case "week" -> now.minusWeeks(1);
|
||||
case "month" -> now.minusMonths(1);
|
||||
case "quarter" -> now.minusMonths(3);
|
||||
case "year" -> now.minusYears(1);
|
||||
default -> now.minusDays(7); // 기본값: 7일
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -66,6 +66,8 @@ public class MinutesEntity extends BaseTimeEntity {
|
||||
.status(this.status)
|
||||
.version(this.version)
|
||||
.createdBy(this.createdBy)
|
||||
.createdAt(this.getCreatedAt())
|
||||
.lastModifiedAt(this.getUpdatedAt())
|
||||
.finalizedBy(this.finalizedBy)
|
||||
.finalizedAt(this.finalizedAt)
|
||||
.build();
|
||||
|
||||
@@ -66,6 +66,8 @@ public class TodoEntity extends BaseTimeEntity {
|
||||
.dueDate(this.dueDate)
|
||||
.status(this.status)
|
||||
.priority(this.priority)
|
||||
.createdAt(this.getCreatedAt())
|
||||
.lastModifiedAt(this.getUpdatedAt())
|
||||
.completedAt(this.completedAt)
|
||||
.build();
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.unicorn.hgzero.meeting.infra.mapper;
|
||||
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Dashboard;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Meeting;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Minutes;
|
||||
import com.unicorn.hgzero.meeting.infra.dto.response.DashboardResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Dashboard 도메인 객체를 Response DTO로 변환하는 매퍼
|
||||
*/
|
||||
@Component
|
||||
public class DashboardResponseMapper {
|
||||
|
||||
/**
|
||||
* Dashboard 도메인 객체를 DashboardResponse로 변환
|
||||
*/
|
||||
public DashboardResponse toResponse(Dashboard dashboard) {
|
||||
if (dashboard == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DashboardResponse.builder()
|
||||
.upcomingMeetings(toUpcomingMeetingResponses(dashboard.getUpcomingMeetings()))
|
||||
.myMinutes(toRecentMinutesResponses(dashboard.getRecentMinutes()))
|
||||
.statistics(toStatisticsResponse(dashboard.getStatistics()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Meeting 목록을 UpcomingMeetingResponse 목록으로 변환
|
||||
*/
|
||||
private List<DashboardResponse.UpcomingMeetingResponse> toUpcomingMeetingResponses(List<Meeting> meetings) {
|
||||
if (meetings == null || meetings.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return meetings.stream()
|
||||
.map(this::toUpcomingMeetingResponse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Meeting을 UpcomingMeetingResponse로 변환
|
||||
*/
|
||||
private DashboardResponse.UpcomingMeetingResponse toUpcomingMeetingResponse(Meeting meeting) {
|
||||
return DashboardResponse.UpcomingMeetingResponse.builder()
|
||||
.meetingId(meeting.getMeetingId())
|
||||
.title(meeting.getTitle())
|
||||
.startTime(meeting.getScheduledAt())
|
||||
.endTime(meeting.getEndTime())
|
||||
.location(meeting.getLocation())
|
||||
.participantCount(meeting.getParticipants() != null ? meeting.getParticipants().size() : 0)
|
||||
.status(meeting.getStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minutes 목록을 RecentMinutesResponse 목록으로 변환
|
||||
*/
|
||||
private List<DashboardResponse.RecentMinutesResponse> toRecentMinutesResponses(List<Minutes> minutesList) {
|
||||
if (minutesList == null || minutesList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return minutesList.stream()
|
||||
.map(this::toRecentMinutesResponse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Minutes를 RecentMinutesResponse로 변환
|
||||
*/
|
||||
private DashboardResponse.RecentMinutesResponse toRecentMinutesResponse(Minutes minutes) {
|
||||
return DashboardResponse.RecentMinutesResponse.builder()
|
||||
.minutesId(minutes.getMinutesId())
|
||||
.title(minutes.getTitle())
|
||||
.meetingDate(minutes.getCreatedAt())
|
||||
.status(minutes.getStatus())
|
||||
.participantCount(0) // Meeting 정보가 필요한데 현재 Minutes에 직접적인 참석자 정보가 없음
|
||||
.lastModified(minutes.getLastModifiedAt() != null ?
|
||||
minutes.getLastModifiedAt() : minutes.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard.Statistics를 StatisticsResponse로 변환
|
||||
*/
|
||||
private DashboardResponse.StatisticsResponse toStatisticsResponse(Dashboard.Statistics statistics) {
|
||||
if (statistics == null) {
|
||||
return DashboardResponse.StatisticsResponse.builder()
|
||||
.upcomingMeetingsCount(0)
|
||||
.todoCompletionRate(0.0)
|
||||
.build();
|
||||
}
|
||||
|
||||
// Todo 완료율 계산
|
||||
double todoCompletionRate = 0.0;
|
||||
int totalTodos = statistics.getTotalTodos() != null ? statistics.getTotalTodos() : 0;
|
||||
int completedTodos = statistics.getCompletedTodos() != null ? statistics.getCompletedTodos() : 0;
|
||||
|
||||
if (totalTodos > 0) {
|
||||
todoCompletionRate = (double) completedTodos / totalTodos * 100.0;
|
||||
}
|
||||
|
||||
return DashboardResponse.StatisticsResponse.builder()
|
||||
.upcomingMeetingsCount(statistics.getScheduledMeetings() != null ?
|
||||
statistics.getScheduledMeetings() : 0)
|
||||
.todoCompletionRate(todoCompletionRate)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user