mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 20:46:23 +00:00
Feat: 대시보드 조회 API 실제 데이터 연동
This commit is contained in:
parent
b7f1352f86
commit
6a2574e9f5
@ -8,7 +8,7 @@ spring:
|
|||||||
datasource:
|
datasource:
|
||||||
url: jdbc:${DB_KIND:postgresql}://${DB_HOST:4.230.48.72}:${DB_PORT:5432}/${DB_NAME:meetingdb}
|
url: jdbc:${DB_KIND:postgresql}://${DB_HOST:4.230.48.72}:${DB_PORT:5432}/${DB_NAME:meetingdb}
|
||||||
username: ${DB_USERNAME:hgzerouser}
|
username: ${DB_USERNAME:hgzerouser}
|
||||||
password: ${DB_PASSWORD:}
|
password: ${DB_PASSWORD:Hi5Jessica!}
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
hikari:
|
hikari:
|
||||||
maximum-pool-size: 20
|
maximum-pool-size: 20
|
||||||
@ -35,7 +35,7 @@ spring:
|
|||||||
redis:
|
redis:
|
||||||
host: ${REDIS_HOST:20.249.177.114}
|
host: ${REDIS_HOST:20.249.177.114}
|
||||||
port: ${REDIS_PORT:6379}
|
port: ${REDIS_PORT:6379}
|
||||||
password: ${REDIS_PASSWORD:}
|
password: ${REDIS_PASSWORD:Hi5Jessica!}
|
||||||
timeout: 2000ms
|
timeout: 2000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
@ -51,7 +51,7 @@ server:
|
|||||||
|
|
||||||
# JWT Configuration
|
# JWT Configuration
|
||||||
jwt:
|
jwt:
|
||||||
secret: ${JWT_SECRET:}
|
secret: ${JWT_SECRET:hgzero-jwt-secret-key-for-dev-environment-only-do-not-use-in-production-minimum-256-bits}
|
||||||
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600}
|
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600}
|
||||||
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:604800}
|
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:604800}
|
||||||
|
|
||||||
@ -125,5 +125,11 @@ api:
|
|||||||
# Azure EventHub Configuration
|
# Azure EventHub Configuration
|
||||||
eventhub:
|
eventhub:
|
||||||
connection-string: ${EVENTHUB_CONNECTION_STRING:}
|
connection-string: ${EVENTHUB_CONNECTION_STRING:}
|
||||||
name: ${EVENTHUB_NAME:hgzero-eventhub-name}
|
name: ${EVENTHUB_NAME:hgzero-events}
|
||||||
consumer-group: ${EVENTHUB_CONSUMER_GROUP:$Default}
|
consumer-group: ${EVENTHUB_CONSUMER_GROUP:$Default}
|
||||||
|
|
||||||
|
# Azure Storage Configuration (for EventHub checkpoints)
|
||||||
|
azure:
|
||||||
|
storage:
|
||||||
|
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:}
|
||||||
|
container: ${AZURE_STORAGE_CONTAINER:hgzero-checkpoints}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,41 @@
|
|||||||
|
-- 회의 참석자 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS meeting_participants (
|
||||||
|
meeting_id VARCHAR(50) NOT NULL,
|
||||||
|
user_id VARCHAR(100) NOT NULL,
|
||||||
|
invitation_status VARCHAR(20) DEFAULT 'PENDING',
|
||||||
|
attended BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (meeting_id, user_id),
|
||||||
|
CONSTRAINT fk_meeting_participants_meeting
|
||||||
|
FOREIGN KEY (meeting_id) REFERENCES meetings(meeting_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 기존 meetings 테이블의 participants 데이터를 meeting_participants 테이블로 마이그레이션
|
||||||
|
INSERT INTO meeting_participants (meeting_id, user_id, invitation_status, attended, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
m.meeting_id,
|
||||||
|
TRIM(participant) as user_id,
|
||||||
|
'PENDING' as invitation_status,
|
||||||
|
FALSE as attended,
|
||||||
|
m.created_at,
|
||||||
|
m.updated_at
|
||||||
|
FROM meetings m
|
||||||
|
CROSS JOIN LATERAL unnest(string_to_array(m.participants, ',')) AS participant
|
||||||
|
WHERE m.participants IS NOT NULL AND m.participants != '';
|
||||||
|
|
||||||
|
-- meetings 테이블에서 participants 컬럼 삭제
|
||||||
|
ALTER TABLE meetings DROP COLUMN IF EXISTS participants;
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX idx_meeting_participants_user_id ON meeting_participants(user_id);
|
||||||
|
CREATE INDEX idx_meeting_participants_invitation_status ON meeting_participants(invitation_status);
|
||||||
|
CREATE INDEX idx_meeting_participants_meeting_id_status ON meeting_participants(meeting_id, invitation_status);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON TABLE meeting_participants IS '회의 참석자 정보';
|
||||||
|
COMMENT ON COLUMN meeting_participants.meeting_id IS '회의 ID';
|
||||||
|
COMMENT ON COLUMN meeting_participants.user_id IS '사용자 ID (이메일)';
|
||||||
|
COMMENT ON COLUMN meeting_participants.invitation_status IS '초대 상태 (PENDING, ACCEPTED, DECLINED)';
|
||||||
|
COMMENT ON COLUMN meeting_participants.attended IS '참석 여부';
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,10 @@
|
|||||||
package com.unicorn.hgzero.meeting.infra.controller;
|
package com.unicorn.hgzero.meeting.infra.controller;
|
||||||
|
|
||||||
import com.unicorn.hgzero.common.dto.ApiResponse;
|
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.dto.response.DashboardResponse;
|
||||||
|
import com.unicorn.hgzero.meeting.infra.mapper.DashboardResponseMapper;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -28,8 +30,11 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class DashboardController {
|
public class DashboardController {
|
||||||
|
|
||||||
|
private final GetDashboardUseCase getDashboardUseCase;
|
||||||
|
private final DashboardResponseMapper dashboardResponseMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대시보드 데이터 조회 (목 데이터)
|
* 대시보드 데이터 조회
|
||||||
*
|
*
|
||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @return 대시보드 데이터
|
* @return 대시보드 데이터
|
||||||
@ -50,80 +55,61 @@ public class DashboardController {
|
|||||||
|
|
||||||
log.info("대시보드 데이터 조회 요청 - userId: {}", userId);
|
log.info("대시보드 데이터 조회 요청 - userId: {}", userId);
|
||||||
|
|
||||||
// 목 데이터 생성
|
try {
|
||||||
DashboardResponse mockResponse = createMockDashboardData();
|
// 실제 데이터 조회
|
||||||
|
Dashboard dashboard = getDashboardUseCase.getDashboard(userId);
|
||||||
|
|
||||||
log.info("대시보드 데이터 조회 완료 - userId: {}", userId);
|
// 도메인 객체를 응답 DTO로 변환
|
||||||
|
DashboardResponse response = dashboardResponseMapper.toResponse(dashboard);
|
||||||
|
|
||||||
return ResponseEntity.ok(ApiResponse.success(mockResponse));
|
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 기간별 대시보드 데이터
|
||||||
*/
|
*/
|
||||||
private DashboardResponse createMockDashboardData() {
|
@Operation(
|
||||||
// 예정된 회의 목 데이터
|
summary = "기간별 대시보드 데이터 조회",
|
||||||
List<DashboardResponse.UpcomingMeetingResponse> upcomingMeetings = Arrays.asList(
|
description = "사용자별 맞춤 대시보드 정보를 기간 필터로 조회합니다.",
|
||||||
DashboardResponse.UpcomingMeetingResponse.builder()
|
security = @SecurityRequirement(name = "bearerAuth")
|
||||||
.meetingId("550e8400-e29b-41d4-a716-446655440001")
|
)
|
||||||
.title("Q1 전략 회의")
|
@GetMapping("/period/{period}")
|
||||||
.startTime(LocalDateTime.now().plusDays(2).withHour(14).withMinute(0))
|
public ResponseEntity<ApiResponse<DashboardResponse>> getDashboardByPeriod(
|
||||||
.endTime(LocalDateTime.now().plusDays(2).withHour(16).withMinute(0))
|
@Parameter(description = "사용자 ID", required = true)
|
||||||
.location("회의실 A")
|
@RequestHeader("X-User-Id") String userId,
|
||||||
.participantCount(5)
|
@Parameter(description = "사용자명", required = true)
|
||||||
.status("SCHEDULED")
|
@RequestHeader("X-User-Name") String userName,
|
||||||
.build(),
|
@Parameter(description = "사용자 이메일", required = true)
|
||||||
DashboardResponse.UpcomingMeetingResponse.builder()
|
@RequestHeader("X-User-Email") String userEmail,
|
||||||
.meetingId("550e8400-e29b-41d4-a716-446655440002")
|
@Parameter(description = "조회 기간", required = true)
|
||||||
.title("개발팀 스프린트 계획")
|
@PathVariable String period) {
|
||||||
.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()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 최근 회의록 목 데이터
|
log.info("기간별 대시보드 데이터 조회 요청 - userId: {}, period: {}", userId, period);
|
||||||
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()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 통계 정보 목 데이터
|
try {
|
||||||
DashboardResponse.StatisticsResponse statistics = DashboardResponse.StatisticsResponse.builder()
|
// 실제 데이터 조회
|
||||||
.upcomingMeetingsCount(2)
|
Dashboard dashboard = getDashboardUseCase.getDashboardByPeriod(userId, period);
|
||||||
.activeTodosCount(0) // activeTodos 제거로 0으로 설정
|
|
||||||
.todoCompletionRate(0.0) // activeTodos 제거로 0으로 설정
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return DashboardResponse.builder()
|
// 도메인 객체를 응답 DTO로 변환
|
||||||
.upcomingMeetings(upcomingMeetings)
|
DashboardResponse response = dashboardResponseMapper.toResponse(dashboard);
|
||||||
.activeTodos(Collections.emptyList()) // activeTodos 빈 리스트로 설정
|
|
||||||
.myMinutes(recentMinutes)
|
log.info("기간별 대시보드 데이터 조회 완료 - userId: {}, period: {}", userId, period);
|
||||||
.statistics(statistics)
|
|
||||||
.build();
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("기간별 대시보드 데이터 조회 실패 - userId: {}, period: {}", userId, period, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -19,8 +19,6 @@ public class DashboardResponse {
|
|||||||
@Schema(description = "예정된 회의 목록")
|
@Schema(description = "예정된 회의 목록")
|
||||||
private final List<UpcomingMeetingResponse> upcomingMeetings;
|
private final List<UpcomingMeetingResponse> upcomingMeetings;
|
||||||
|
|
||||||
@Schema(description = "진행 중 Todo 목록")
|
|
||||||
private final List<ActiveTodoResponse> activeTodos;
|
|
||||||
|
|
||||||
@Schema(description = "최근 회의록 목록")
|
@Schema(description = "최근 회의록 목록")
|
||||||
private final List<RecentMinutesResponse> myMinutes;
|
private final List<RecentMinutesResponse> myMinutes;
|
||||||
@ -36,9 +34,6 @@ public class DashboardResponse {
|
|||||||
.upcomingMeetings(dto.getUpcomingMeetings().stream()
|
.upcomingMeetings(dto.getUpcomingMeetings().stream()
|
||||||
.map(UpcomingMeetingResponse::from)
|
.map(UpcomingMeetingResponse::from)
|
||||||
.toList())
|
.toList())
|
||||||
.activeTodos(dto.getActiveTodos().stream()
|
|
||||||
.map(ActiveTodoResponse::from)
|
|
||||||
.toList())
|
|
||||||
.myMinutes(dto.getMyMinutes().stream()
|
.myMinutes(dto.getMyMinutes().stream()
|
||||||
.map(RecentMinutesResponse::from)
|
.map(RecentMinutesResponse::from)
|
||||||
.toList())
|
.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
|
@Getter
|
||||||
@Builder
|
@Builder
|
||||||
@ -159,8 +121,6 @@ public class DashboardResponse {
|
|||||||
@Schema(description = "예정된 회의 수", example = "2")
|
@Schema(description = "예정된 회의 수", example = "2")
|
||||||
private final Integer upcomingMeetingsCount;
|
private final Integer upcomingMeetingsCount;
|
||||||
|
|
||||||
@Schema(description = "진행 중 Todo 수", example = "5")
|
|
||||||
private final Integer activeTodosCount;
|
|
||||||
|
|
||||||
@Schema(description = "Todo 완료율", example = "68.5")
|
@Schema(description = "Todo 완료율", example = "68.5")
|
||||||
private final Double todoCompletionRate;
|
private final Double todoCompletionRate;
|
||||||
@ -168,7 +128,6 @@ public class DashboardResponse {
|
|||||||
public static StatisticsResponse from(DashboardDTO.StatisticsDTO dto) {
|
public static StatisticsResponse from(DashboardDTO.StatisticsDTO dto) {
|
||||||
return StatisticsResponse.builder()
|
return StatisticsResponse.builder()
|
||||||
.upcomingMeetingsCount(dto.getUpcomingMeetingsCount())
|
.upcomingMeetingsCount(dto.getUpcomingMeetingsCount())
|
||||||
.activeTodosCount(dto.getActiveTodosCount())
|
|
||||||
.todoCompletionRate(dto.getTodoCompletionRate())
|
.todoCompletionRate(dto.getTodoCompletionRate())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
package com.unicorn.hgzero.meeting.infra.gateway;
|
package com.unicorn.hgzero.meeting.infra.gateway;
|
||||||
|
|
||||||
import com.unicorn.hgzero.meeting.biz.domain.Dashboard;
|
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.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.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.MinutesJpaRepository;
|
||||||
import com.unicorn.hgzero.meeting.infra.gateway.repository.TodoJpaRepository;
|
import com.unicorn.hgzero.meeting.infra.gateway.repository.TodoJpaRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -11,6 +17,11 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
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 구현체
|
* 대시보드 Gateway 구현체
|
||||||
@ -22,106 +33,227 @@ import java.time.LocalDateTime;
|
|||||||
public class DashboardGateway implements DashboardReader {
|
public class DashboardGateway implements DashboardReader {
|
||||||
|
|
||||||
private final MeetingJpaRepository meetingJpaRepository;
|
private final MeetingJpaRepository meetingJpaRepository;
|
||||||
|
private final MeetingParticipantJpaRepository meetingParticipantJpaRepository;
|
||||||
private final MinutesJpaRepository minutesJpaRepository;
|
private final MinutesJpaRepository minutesJpaRepository;
|
||||||
private final TodoJpaRepository todoJpaRepository;
|
private final TodoJpaRepository todoJpaRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dashboard getDashboardByUserId(String userId) {
|
public Dashboard getDashboardByUserId(String userId) {
|
||||||
log.debug("Getting dashboard for user: {}", userId);
|
log.info("대시보드 데이터 조회 시작 - userId: {}", userId);
|
||||||
|
|
||||||
// 회의 통계 조회
|
// 1. 다가오는 회의 목록 조회 (향후 30일, 최대 10개)
|
||||||
long totalMeetings = meetingJpaRepository.findByOrganizerId(userId).size();
|
List<Meeting> upcomingMeetings = getUpcomingMeetings(userId);
|
||||||
long scheduledMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "SCHEDULED").size();
|
|
||||||
long inProgressMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "IN_PROGRESS").size();
|
|
||||||
long completedMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "COMPLETED").size();
|
|
||||||
|
|
||||||
// 회의록 통계 조회
|
// 2. 최근 회의록 목록 조회 (최근 7일, 최대 10개)
|
||||||
long totalMinutes = minutesJpaRepository.findByCreatedBy(userId).size();
|
List<Minutes> recentMinutes = getRecentMinutes(userId);
|
||||||
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 통계 조회
|
// 3. 통계 정보 계산 (최근 30일 기준)
|
||||||
long totalTodos = todoJpaRepository.findByAssigneeId(userId).size();
|
Dashboard.Statistics statistics = calculateStatistics(userId);
|
||||||
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 dashboard = Dashboard.builder()
|
||||||
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()
|
|
||||||
.userId(userId)
|
.userId(userId)
|
||||||
|
.period("7days")
|
||||||
|
.upcomingMeetings(upcomingMeetings)
|
||||||
|
.recentMinutes(recentMinutes)
|
||||||
|
.assignedTodos(new ArrayList<>())
|
||||||
.statistics(statistics)
|
.statistics(statistics)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
log.info("대시보드 데이터 조회 완료 - userId: {}, 예정 회의: {}개, 최근 회의록: {}개",
|
||||||
|
userId, upcomingMeetings.size(), recentMinutes.size());
|
||||||
|
|
||||||
|
return dashboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dashboard getDashboardByUserIdAndPeriod(String userId, String period) {
|
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 startTime = calculateStartTime(period);
|
||||||
LocalDateTime endTime = LocalDateTime.now();
|
LocalDateTime endTime = LocalDateTime.now();
|
||||||
|
|
||||||
// 기간 내 회의 통계 조회
|
// 1. 기간 내 다가오는 회의 목록 조회
|
||||||
long totalMeetings = meetingJpaRepository.findByOrganizerId(userId).stream()
|
List<Meeting> upcomingMeetings = getUpcomingMeetingsByPeriod(userId, startTime, endTime);
|
||||||
.filter(m -> m.getScheduledAt().isAfter(startTime) && m.getScheduledAt().isBefore(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();
|
.count();
|
||||||
|
|
||||||
long scheduledMeetings = meetingJpaRepository.findByOrganizerIdAndStatus(userId, "SCHEDULED").stream()
|
return Dashboard.Statistics.builder()
|
||||||
.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()
|
|
||||||
.totalMeetings((int) totalMeetings)
|
.totalMeetings((int) totalMeetings)
|
||||||
.scheduledMeetings((int) scheduledMeetings)
|
.scheduledMeetings((int) scheduledMeetings)
|
||||||
.inProgressMeetings((int) inProgressMeetings)
|
.inProgressMeetings((int) inProgressMeetings)
|
||||||
@ -134,13 +266,6 @@ public class DashboardGateway implements DashboardReader {
|
|||||||
.completedTodos((int) completedTodos)
|
.completedTodos((int) completedTodos)
|
||||||
.overdueTodos((int) overdueTodos)
|
.overdueTodos((int) overdueTodos)
|
||||||
.build();
|
.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) {
|
private LocalDateTime calculateStartTime(String period) {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
return switch (period.toUpperCase()) {
|
return switch (period.toLowerCase()) {
|
||||||
case "WEEK" -> now.minusWeeks(1);
|
case "1day" -> now.minusDays(1);
|
||||||
case "MONTH" -> now.minusMonths(1);
|
case "3days" -> now.minusDays(3);
|
||||||
case "QUARTER" -> now.minusMonths(3);
|
case "7days" -> now.minusDays(7);
|
||||||
case "YEAR" -> now.minusYears(1);
|
case "30days" -> now.minusDays(30);
|
||||||
default -> now.minusMonths(1); // 기본값: 1개월
|
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일
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,8 @@ public class MinutesEntity extends BaseTimeEntity {
|
|||||||
.status(this.status)
|
.status(this.status)
|
||||||
.version(this.version)
|
.version(this.version)
|
||||||
.createdBy(this.createdBy)
|
.createdBy(this.createdBy)
|
||||||
|
.createdAt(this.getCreatedAt())
|
||||||
|
.lastModifiedAt(this.getUpdatedAt())
|
||||||
.finalizedBy(this.finalizedBy)
|
.finalizedBy(this.finalizedBy)
|
||||||
.finalizedAt(this.finalizedAt)
|
.finalizedAt(this.finalizedAt)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@ -66,6 +66,8 @@ public class TodoEntity extends BaseTimeEntity {
|
|||||||
.dueDate(this.dueDate)
|
.dueDate(this.dueDate)
|
||||||
.status(this.status)
|
.status(this.status)
|
||||||
.priority(this.priority)
|
.priority(this.priority)
|
||||||
|
.createdAt(this.getCreatedAt())
|
||||||
|
.lastModifiedAt(this.getUpdatedAt())
|
||||||
.completedAt(this.completedAt)
|
.completedAt(this.completedAt)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user