[FE] 대시보드 지표 카드 UI 추가 및 SSE 연결, 각 지표들의 실시간 데이터 업데이트 기능 추가#334
[FE] 대시보드 지표 카드 UI 추가 및 SSE 연결, 각 지표들의 실시간 데이터 업데이트 기능 추가#334lee0jae330 merged 135 commits intodevelopfrom
Conversation
There was a problem hiding this comment.
Pull request overview
이 PR은 대시보드 페이지에 실제 지표 카드를 추가하고 SSE(Server-Sent Events)를 통한 실시간 데이터 업데이트 기능을 구현한 대규모 작업입니다. 주요 변경 사항으로는 대시보드 카드별 전용 컴포넌트 렌더링, SSE 연결 및 구독 관리, API 연동, DTO/타입 리팩토링, 로딩/에러 처리 개선이 포함되어 있습니다.
Changes:
- 대시보드 메인에서 카드 코드별 전용 컴포넌트를 렌더링하도록 구조 변경 (매출/메뉴 분석 지표 카드 추가)
- SSE 클라이언트를 통한 실시간 데이터 갱신 기능 구현 (연결, 구독/해지, 캐시 업데이트)
- 매출 유형/주문수단/결제수단 관련 DTO 및 타입을 서버 응답 구조에 맞게 리팩토링 (OrderMethod → OrderChannel)
- FetchBoundary 및 빈 상태 UI 추가, 요일 인덱싱 로직 수정 등 여러 개선 사항 반영
Reviewed changes
Copilot reviewed 97 out of 101 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/hooks/dashboard/useDashboardSseConnection.ts | SSE 연결 관리 및 카드별 실시간 데이터 업데이트 로직 |
| frontend/src/hooks/dashboard/useDashboardCardSubscription.ts | 대시보드 카드 구독/해지 관리 훅 |
| frontend/src/hooks/dashboard/useDashboardCardList.ts | 대시보드 카드 목록 조회 훅 |
| frontend/src/hooks/dashboard/useDashboardCardDetailQueryOption.ts | 카드 상세 쿼리 옵션 생성 훅 |
| frontend/src/components/dashboard/dashboard-main/DashboardCard.tsx | 카드 코드별 컴포넌트 매핑 및 렌더링 |
| frontend/src/components/dashboard/dashboard-main/DashboardMain.tsx | 대시보드 메인 로직 단순화 및 구독 훅 적용 |
| frontend/src/components/dashboard/dashboard-main/DashboardMainContent.tsx | 카드 렌더링 구조를 FetchBoundary 및 DefaultCardWrapper로 변경 |
| frontend/src/components/dashboard/dashboard-sales/* | 매출 관련 지표 카드 컴포넌트 (9개 파일) |
| frontend/src/components/dashboard/dashboard-menu/* | 메뉴 관련 지표 카드 컴포넌트 (4개 파일) |
| frontend/src/components/sales/dashboard-sales-income/OrderChannelContent.tsx | OrderMethod에서 OrderChannel로 이름 변경 |
| frontend/src/components/sales/dashboard-sales-income/SalesTypeContent.tsx | 매출 유형별 데이터 매핑 로직 업데이트 |
| frontend/src/components/sales/dashboard-sales-income/PaymentMethodContent.tsx | 결제수단별 데이터 매핑 로직 업데이트 |
| frontend/src/components/sales/dashboard-sales-pattern/PeakTimeContent.tsx | null 처리 로직 추가 및 요일 인덱싱 수정 |
| frontend/src/components/sales/dashboard-sales-pattern/SalesByDayContent.tsx | null 처리 및 showYGuideLine 제거 |
| frontend/src/components/sales/dashboard-sales-trend/SalesTrendContent.tsx | 차트 레이아웃 구조 변경 |
| frontend/src/components/menu/dashboard-menu-order/TimeSlotMenuOrderCountCardContent.tsx | 빈 상태 처리 추가 |
| frontend/src/components/menu/dashboard-menu-combination/PopularMenuCombinationCardContent.tsx | 빈 상태 처리 추가 |
| frontend/src/components/shared/pagination/* | pagenation에서 pagination으로 폴더명 수정 |
| frontend/src/components/shared/line-chart/LineChart.tsx | 라인 시리즈 렌더링 순서 변경 |
| frontend/src/services/shared/sseClient.ts | AbortController 중복 abort 방지 로직 추가 |
| frontend/src/services/dashboard/* | 대시보드 SSE 구독 API 및 분석 상세 API 추가 |
| frontend/src/services/analysis/get.ts | 분석 상세 조회 서비스 추가 |
| frontend/src/types/sales/dto/* | 매출 관련 DTO 타입 리팩토링 (RealTimeSales → RealSales, OrderMethod → OrderChannel) |
| frontend/src/types/sales/dashboard-sales-income/salesIncomeStructureInsight.ts | 제네릭 타입으로 리팩토링 |
| frontend/src/types/menu/dto/getPopularMenuCombinationDto.ts | nullable 필드 추가 및 오타 수정 |
| frontend/src/types/dashboard/dto/* | SSE 구독 요청 DTO 추가 |
| frontend/src/constants/sales/salesSource.ts | TAKEOUT → TAKE_OUT, MOBILE → EASY_PAY 이름 변경 |
| frontend/src/constants/sales/dashboard-sales-income/* | OrderMethod에서 OrderChannel로 변경 및 예시 데이터 타입 개선 |
| frontend/src/constants/dashboard/dashboardMetric.ts | 카드 코드별 타입 가드 함수 추가 |
| frontend/src/utils/sales/* | 요일 인덱싱 수정 및 타입 변경 반영 |
| frontend/src/utils/shared/bar-chart/getBarHeight.ts | 주석을 한국어로 변경 |
| frontend/src/mocks/auth/authHandler.ts | passthrough 추가 (테스트 용도) |
| frontend/src/mocks/analysis/analysisHandler.ts | 분석 API 모킹 핸들러 추가 |
Comments suppressed due to low confidence (2)
frontend/src/components/sales/dashboard-sales-income/OrderChannelContent.tsx:48
- 타입 캐스팅이 필요한 이유를 확인해주세요.
SalesIncomeStructureInsight<Extract<keyof typeof SALES_SOURCE, 'ORDER_METHOD'>>로 정의된 타입에서topType은 이미keyof typeof SALES_SOURCE.ORDER_METHOD타입이어야 하는데, 여기서as캐스팅을 사용하고 있습니다.
타입 정의에 문제가 있거나, 불필요한 타입 캐스팅일 가능성이 있습니다. 타입 정의를 수정하거나 캐스팅이 정말 필요한지 재검토해 주세요.
frontend/src/mocks/auth/authHandler.ts:39
- MSW 핸들러에서
passthrough()를 호출한 후 그 아래의 코드가 실행되지 않습니다.passthrough()이후의 코드(15-39줄)는 unreachable code입니다.
디버깅/테스트 용도로 임시로 passthrough를 추가한 것이라면, 이를 제거하거나 조건부로 사용하도록 수정해 주세요.
| const newItems = structuredClone(oldData.items); | ||
|
|
||
| const firstItem = newItems[0]; | ||
| if (!firstItem) { | ||
| return { | ||
| items: newItems, | ||
| }; | ||
| } | ||
|
|
||
| firstItem.timeSlot2H = timeSlot2H; | ||
|
|
||
| const firstMenu = firstItem.menus?.[0]; | ||
| if (firstMenu) { | ||
| firstMenu.menuName = menuName; | ||
| } | ||
|
|
||
| return { | ||
| items: newItems, | ||
| }; | ||
| }, | ||
| [], | ||
| ); | ||
|
|
||
| /** | ||
| * 인기 메뉴 조합 쿼리 데이터 업데이트 함수 | ||
| */ | ||
| const updatePopularMenuCombinationData = useCallback( | ||
| (response: GetDashboardPopularMenuCombinationResponseDto) => | ||
| (oldData?: GetPopularMenuCombinationResponseDto) => { | ||
| const { firstMenuName, secondMenuName } = response; | ||
|
|
||
| if (!oldData) { | ||
| return oldData; | ||
| } | ||
|
|
||
| // 깊은 복사 후 해당 객체 수정 | ||
| const newItems = structuredClone(oldData.items); |
There was a problem hiding this comment.
structuredClone을 사용할 때 주의가 필요합니다. 이 API는 모든 브라우저에서 지원되는 것은 아니며, 특히 Safari 15.3 이하 버전에서는 지원되지 않습니다.
프로젝트에서 지원하는 브라우저 범위를 확인하고, 필요시 폴리필을 추가하거나 대체 방법(예: JSON.parse/stringify, lodash cloneDeep 등)을 사용하는 것을 고려해 주세요.
| }); | ||
| } | ||
| }; | ||
| }, [cardList, subscribeDashboardCardList, unsubscribeDashboardCardList]); |
There was a problem hiding this comment.
useEffect의 의존성 배열에 mutation 함수가 포함되어 있습니다. useMutation에서 반환된 mutate 함수는 매 렌더링마다 새로 생성되므로, 이를 의존성 배열에 포함하면 effect가 매번 실행되어 불필요한 구독/해지가 반복됩니다.
mutation 함수를 의존성 배열에서 제거하거나, useCallback으로 감싼 wrapper 함수를 사용하는 것이 좋습니다.
frontend/src/components/dashboard/dashboard-main/DashboardCard.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
제 의견으로는 analysis 도메인이 services와 types 쪽에만 신설되는 게 조금 어색하게 느껴집니다.
대시보드 카드 조회 & 각 도메인 상세분석 조회에 모두 쓰이니 shared로 보내는 게 어떨까 싶습니다!
There was a problem hiding this comment.
근데 shared는 정말 모든 도메인에서 사용할 수 있어야 돼서 shared에 두기에는 애매하지 않나요 ?? 그래서 analysis 도메인을 따로 만들었습니다
There was a problem hiding this comment.
애매하네요... chart나 card wrapper 같은 컴포넌트는 또 shared에 있어서요~
components도 리팩터링 하면서 analysis 도메인을 만들어도 될 것 같구요
|
[p1] |
frontend/src/components/dashboard/dashboard-main/DashboardMainContent.tsx
Show resolved
Hide resolved
| > | ||
| 카드 {item.cardCode} | ||
| </div> | ||
| <FetchBoundary key={`dashboard-card-${item.cardCode}`}> |
There was a problem hiding this comment.
[p4] 여기에 DefaultCardFetchBoundary 컴포넌트 사용할 수 있을 것 같네요
frontend/src/components/dashboard/dashboard-sales/DashboardPeakTimeCard.tsx
Show resolved
Hide resolved
frontend/src/components/shared/default-card-wrapper/DefaultCardWrapper.tsx
Show resolved
Hide resolved
| const getEmptyViewMessage = ( | ||
| period: PeriodType<typeof PERIOD_PRESET_KEYS.today7_30>, | ||
| ) => { | ||
| let dateMessage = ''; |
There was a problem hiding this comment.
[p3] dateMessage = period라고 하실 수 있습니다.
| const handleSseError = useCallback(() => { | ||
| retryCountRef.current++; | ||
| // 지수 백오프 | ||
| const backoff = Math.min( |
There was a problem hiding this comment.
[p5] 지수승으로 Retry 하는 거 좋습니다~
#️⃣ 변경 사항
FetchBoundary,SuspenseSkeleton)와 빈 상태 UI를 보강했습니다.#️⃣ 작업 상세 내용
DashboardMain/DashboardMainContent에서 카드 코드별 전용 컴포넌트(DashboardCard)를 렌더링하도록 변경FetchBoundary적용으로 카드 단위 로딩/에러 격리useDashboardSseConnection추가:/api/sse/connection수신 이벤트를 카드별 query cache에 반영useDashboardCardSubscription추가: 현재 대시보드의 카드 코드(topic) 구독/해지 API 호출dashboardId기반으로 전환하고 탭 목록 API와 동기화PR 포인트
useDashboardSseConnection훅에서 각 query data를 업데이트하도록 했습니다useDashboardSseConnection,useDashboardSubscription,dashboardMainContent위주로 봐주시면 감사하겠습니다#️⃣ 관련 이슈
📸 스크린샷 (선택)
변경 전
변경 후
2026-02-19.8.51.47.mov
2026-02-19.8.52.18.mov
2026-02-19.8.52.56.mov
2026-02-19.8.53.44.mov
Empty View 추가
📎 참고할만한 자료 (선택)
frontend/src/services/shared/sseClient.tsfrontend/src/hooks/dashboard/useDashboardSseConnection.tsfrontend/src/hooks/dashboard/useDashboardCardSubscription.tsfrontend/src/components/dashboard/dashboard-main/DashboardMain.tsx