[Feature/#113] 대시보드 전체 집계 API 연동 - #121
Conversation
📝 WalkthroughWalkthrough대시보드 메트릭 API 클라이언트( Changes
Sequence Diagram(s)sequenceDiagram
participant Component as OverviewDashboard
participant Hook as useOverviewMetrics
participant Store as useWorkspaceStore
participant Query as useCoreQuery
participant API as getOverview
participant Server as Backend
Component->>Store: selectedOrgId 읽기
Store-->>Component: orgId
Component->>Hook: useOverviewMetrics()
Hook->>Store: selectedOrgId 읽기
Store-->>Hook: orgId
Hook->>Query: 쿼리 실행 요청 (key: ["overview","metrics",orgId])
Query->>API: getOverview(orgId[, providerType])
API->>Server: GET /api/dashboard/{orgId}/metrics
Server-->>API: ICommonResponse<IMetricsResponse>
API-->>Query: IMetricsResponse (data.payload)
Query->>Hook: select: toKpis 변환 -> IStatCardProps[]
Hook-->>Component: kpis, isLoading, isError
Component->>Component: StatCard 렌더링 / skeleton / 에러 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.Add a configuration file to your project to customize how CodeRabbit runs |
📚 Storybook 배포 완료
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/ads/list/CampaignDetail.tsx (1)
18-22:⚠️ Potential issue | 🟠 Major
orgId소스가 일관되지 않습니다.
useCampaignDetail()훅은 내부적으로useWorkspaceStore에서selectedOrgId를 가져오는데,updateCampaignStatus호출(Line 148, 169-172)에서는 여전히 URL 파라미터의orgId를 사용하고 있어요.만약 URL의
orgId와 스토어의selectedOrgId가 다를 경우, 조회와 수정 대상이 달라질 수 있습니다. 일관성을 위해 하나의 소스를 사용하는 것이 좋겠습니다.🔧 수정 방안
스토어에서 orgId를 가져와 사용하거나, 아니면 useCampaignDetail 훅에서 orgId를 반환받아 사용하는 방식으로 통일하세요:
+ import useWorkspaceStore from "@/store/useWorkspaceStore"; export default function CampaignDetail() { - const { orgId, projectId } = useParams<{ - orgId: string; - projectId: string; - }>(); + const { projectId } = useParams<{ projectId: string }>(); + const orgId = useWorkspaceStore((s) => s.selectedOrgId); const { data, isLoading, refetch } = useCampaignDetail();그 후
updateCampaignStatus호출 시Number(orgId)대신orgId를 직접 사용하면 됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/ads/list/CampaignDetail.tsx` around lines 18 - 22, The code is using two different sources for orgId (URL params via useParams and the store via useCampaignDetail/useWorkspaceStore), causing potential mismatches; unify to one source by either (A) reading orgId from the workspace store (selectedOrgId) where useCampaignDetail gets it and replace Number(orgId) calls in updateCampaignStatus with the store value, or (B) modify useCampaignDetail to return the orgId from params and use that returned orgId everywhere; update all calls to updateCampaignStatus (references in this file) to consume the single unified orgId (useCampaignDetail or selectedOrgId) rather than the raw useParams orgId.
🧹 Nitpick comments (3)
src/hooks/dashboard/useOverviewMetrics.ts (1)
10-10:toRate함수의 의도를 명확히 해주세요.
toRate가Math.abs(rate)를 사용하는 이유가 있을까요? 변화율이 음수일 때 방향(direction)은down으로 표시하면서, 값 자체는 절댓값으로 보여주려는 의도 같은데, 간단한 주석이 있으면 이해하기 쉬울 것 같습니다.📝 주석 추가 제안
+// 변화율을 절댓값 퍼센트로 표시 (방향은 trend.direction으로 표현) const toRate = (rate: number) => `${(Math.abs(rate) * 100).toFixed(1)}%`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/dashboard/useOverviewMetrics.ts` at line 10, The toRate function uses Math.abs(rate) which hides the sign; add a brief comment above the toRate declaration explaining that the function intentionally returns the magnitude formatted as a percent and that the sign/direction (e.g., "up"/"down") is handled separately by the component/logic that consumes toRate (so negative rates still render as "down" while the displayed percent is absolute); reference the toRate function name so reviewers can find and verify the rationale.src/store/useWorkspaceStore.ts (1)
3-6:selectedOrgId를 초기화하는 기능이 없어요.현재
setSelectedOrgId는number타입만 받도록 되어 있어서, 선택된 조직을 초기화하거나 로그아웃 시null로 되돌릴 방법이 없습니다. 나중에 워크스페이스 전환이나 로그아웃 기능 구현 시 필요할 수 있으니 참고해주세요.♻️ 초기화 기능 추가 제안
interface IWorkspaceState { selectedOrgId: number | null; - setSelectedOrgId: (orgId: number) => void; + setSelectedOrgId: (orgId: number | null) => void; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/useWorkspaceStore.ts` around lines 3 - 6, The IWorkspaceState interface lacks a way to clear the selected org; change the setSelectedOrgId signature to accept number | null (i.e., setSelectedOrgId: (orgId: number | null) => void) and update any implementations of setSelectedOrgId (creating store/updater functions) to allow calling setSelectedOrgId(null) to reset selectedOrgId; ensure selectedOrgId remains typed as number | null and adjust any callers to handle null cases.src/layout/main/MainLayout.tsx (1)
14-22: 워크스페이스 로드 실패 시 처리가 없습니다.워크스페이스 조회가 실패할 경우
selectedOrgId가null로 유지되면서 하위 컴포넌트들의 API 호출이 비활성화됩니다. 이 상태에서 사용자가 인지할 수 있는 피드백이 없어요.♻️ 에러 상태 처리 추가 예시
- const { data: workspaces } = useCoreQuery(["workspaces"], getMyWorkspaces); + const { data: workspaces, isError } = useCoreQuery(["workspaces"], getMyWorkspaces); useEffect(() => { if (workspaces && workspaces.length > 0) { setSelectedOrgId(workspaces[0].orgId); } }, [workspaces, setSelectedOrgId]); + // 워크스페이스 로드 실패 시 토스트 또는 에러 바운더리로 처리 고려🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/layout/main/MainLayout.tsx` around lines 14 - 22, The current useEffect assumes workspaces from useCoreQuery always succeeds and only sets setSelectedOrgId when workspaces exist; add explicit error and loading handling for useCoreQuery so that when the query fails you clear or explicitly setSelectedOrgId(null) and surface user feedback (banner/toast/error component) so downstream components know why API calls are disabled; update the logic around useCoreQuery(["workspaces"], getMyWorkspaces) and the effect that references workspaces and setSelectedOrgId to handle { data, error, isLoading } (or equivalent) and branch: set id to first orgId when data present, set to null on error, and trigger a visible error UI message so users are informed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/workspace/InviteMemberModal.tsx`:
- Line 70: InviteMemberModal 및 MemberItem에서 Tailwind에 정의되지 않은 커스텀 클래스가 제거될 위험이
있으므로 클래스명을 임의 값 문법으로 교체하세요: InviteMemberModal의 className에서 max-w-190을
max-w-[190px]로 바꾸고 min-w-22를 min-w-[22px]로 변경하고, MemberItem의 min-w-24.5(소수점
spacing 사용)는 적절한 단위로 바꿔 예를 들어 min-w-[24.5rem] 또는 원하는 px/rem 값으로 변경해 Tailwind가 빌드
시 보존하도록 하세요.
In `@src/hooks/ads/useCampaignDetail.ts`:
- Around line 6-15: The hook useCampaignDetail currently reads orgId from the
global store which can diverge from the URL; change it to read both orgId and
projectId from route params (useParams) and use those values as the source of
truth in the query key and request (e.g., replace useWorkspaceStore usage with
the route param), parse/validate projectId as a number before calling
getCampaignDetail, and set the query's enabled flag to only true when both route
orgId and a valid numeric projectId are present so the request is only sent for
validated route identifiers.
In `@src/pages/ads/list/AdsListPage.tsx`:
- Around line 24-28: The component currently masks API failures by defaulting
data to [] and only checking isLoading; update the useCoreQuery call in
AdsListPage to also destructure isError and error (e.g., const { data:
campaigns, isLoading, isError, error } = useCoreQuery<ICampaign[]>(["campaigns",
orgId], () => getCampaignList(orgId!), { enabled: !!orgId });) and remove the
unconditional default [] so you can distinguish between empty list and failed
request; then add UI handling in the AdsListPage render to show an error
state/message (with error.message) and a retry action when isError is true,
while preserving the existing loading and empty-list flows.
- Around line 30-43: The onSuccess callbacks for stopAll and resumeAll currently
duplicate the toast already shown in useControlModal.handleConfirm and don't
refresh the campaigns cache; replace the duplicate toast logic in the onSuccess
for stopAll and resumeAll with a call to queryClient.invalidateQueries({
queryKey: ["campaigns", orgId] }) so the campaigns list (and hasActiveCampaign)
is refetched after a successful confirm; locate these callbacks on the stopAll
and resumeAll useControlModal invocations and remove the toast.success calls
while importing/using the existing queryClient instance to call
invalidateQueries with the ["campaigns", orgId] key.
In `@src/pages/dashboard/overview/OverviewDashboard.tsx`:
- Line 37: The component uses useOverviewMetrics() but only reads data (kpis)
and doesn't handle loading/error states; update OverviewDashboard to destructure
isLoading and isError from useOverviewMetrics() alongside data and use them to
conditionally render a loading skeleton or spinner when isLoading is true and an
error message/UI placeholder when isError is true (instead of rendering an empty
KPI grid). Locate the call to useOverviewMetrics and the KPI grid render in
OverviewDashboard and add explicit conditional returns or gated rendering based
on isLoading/isError so users see feedback on API failure or pending fetches.
---
Outside diff comments:
In `@src/pages/ads/list/CampaignDetail.tsx`:
- Around line 18-22: The code is using two different sources for orgId (URL
params via useParams and the store via useCampaignDetail/useWorkspaceStore),
causing potential mismatches; unify to one source by either (A) reading orgId
from the workspace store (selectedOrgId) where useCampaignDetail gets it and
replace Number(orgId) calls in updateCampaignStatus with the store value, or (B)
modify useCampaignDetail to return the orgId from params and use that returned
orgId everywhere; update all calls to updateCampaignStatus (references in this
file) to consume the single unified orgId (useCampaignDetail or selectedOrgId)
rather than the raw useParams orgId.
---
Nitpick comments:
In `@src/hooks/dashboard/useOverviewMetrics.ts`:
- Line 10: The toRate function uses Math.abs(rate) which hides the sign; add a
brief comment above the toRate declaration explaining that the function
intentionally returns the magnitude formatted as a percent and that the
sign/direction (e.g., "up"/"down") is handled separately by the component/logic
that consumes toRate (so negative rates still render as "down" while the
displayed percent is absolute); reference the toRate function name so reviewers
can find and verify the rationale.
In `@src/layout/main/MainLayout.tsx`:
- Around line 14-22: The current useEffect assumes workspaces from useCoreQuery
always succeeds and only sets setSelectedOrgId when workspaces exist; add
explicit error and loading handling for useCoreQuery so that when the query
fails you clear or explicitly setSelectedOrgId(null) and surface user feedback
(banner/toast/error component) so downstream components know why API calls are
disabled; update the logic around useCoreQuery(["workspaces"], getMyWorkspaces)
and the effect that references workspaces and setSelectedOrgId to handle { data,
error, isLoading } (or equivalent) and branch: set id to first orgId when data
present, set to null on error, and trigger a visible error UI message so users
are informed.
In `@src/store/useWorkspaceStore.ts`:
- Around line 3-6: The IWorkspaceState interface lacks a way to clear the
selected org; change the setSelectedOrgId signature to accept number | null
(i.e., setSelectedOrgId: (orgId: number | null) => void) and update any
implementations of setSelectedOrgId (creating store/updater functions) to allow
calling setSelectedOrgId(null) to reset selectedOrgId; ensure selectedOrgId
remains typed as number | null and adjust any callers to handle null cases.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c6a6e569-5004-4754-8033-25f624d92c37
📒 Files selected for processing (13)
src/api/dashboard/overview.tssrc/api/workspace/org.tssrc/components/workspace/InviteMemberModal.tsxsrc/hooks/ads/useCampaignDetail.tssrc/hooks/dashboard/useOverviewMetrics.tssrc/layout/main/MainLayout.tsxsrc/pages/ads/list/AdsListPage.tsxsrc/pages/ads/list/CampaignDetail.tsxsrc/pages/dashboard/overview/OverviewDashboard.tsxsrc/pages/dashboard/overview/overview.mock.tssrc/store/useWorkspaceStore.tssrc/types/dashboard/overview.tssrc/types/workspace/workspace.ts
💤 Files with no reviewable changes (2)
- src/types/workspace/workspace.ts
- src/pages/dashboard/overview/overview.mock.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/pages/ads/list/AdsListPage.tsx (1)
25-29:⚠️ Potential issue | 🟠 Major이전 리뷰와 동일하게, 실패 상태가 빈 목록으로 숨겨집니다.
data: campaigns = []와isLoading분기만 두면, 캠페인 조회 실패와orgId미확정으로 인한 미조회 상태가 모두 기본 렌더링으로 내려갑니다. 지금 구조에서는 네트워크/권한 오류가 나도 사용자가 그냥 "캠페인이 없음"으로 오해할 수 있어서,isError,error,!orgId를 빈 목록 처리보다 먼저 분기하는 게 안전합니다.campaigns기본값은 그 다음에data ?? []로 계산해 주세요.수정 방향 예시
- const { data: campaigns = [], isLoading } = useCoreQuery<ICampaign[]>( + const { + data, + isLoading, + isError, + error, + } = useCoreQuery<ICampaign[]>( ["campaigns", orgId], () => getCampaignList(orgId!), { enabled: !!orgId }, ); + const campaigns = data ?? [];As per coding guidelines,
src/**:1. 상태 관리: 서버 상태(React Query)와 전역 상태(Zustand)의 분리 여부 확인. useMutation, useQuery의 올바른 사용 확인. 6. 에러 처리: API 실패 대응 및 사용자 피드백 적절성 검토.아래 스크립트에서
AdsListPage에isError/error분기가 없고useCoreQuery가 해당 상태를 호출부에 노출한다면, 이 이슈가 현재 코드에도 그대로 남아 있는지 바로 확인할 수 있습니다.#!/bin/bash set -euo pipefail # AdsListPage가 로딩 외 상태를 어떻게 분기하는지 확인 nl -ba src/pages/ads/list/AdsListPage.tsx | sed -n '20,90p' # 현재 파일에서 query error 상태를 실제로 사용하는지 확인 rg -n --type tsx '\bisError\b|\berror\b' src/pages/ads/list/AdsListPage.tsx || true # useCoreQuery가 error 상태를 호출부에 노출하는지 확인 fd -t f 'customQuery\.(ts|tsx)$' src | xargs -r -I{} sh -c 'echo "== {} =="; nl -ba "{}" | sed -n "1,220p"'Also applies to: 58-66
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/ads/list/AdsListPage.tsx` around lines 25 - 29, The AdsListPage currently hides query failures by defaulting campaigns via `data: campaigns = []` and only checking `isLoading`; update the render logic in `AdsListPage` to first check the query error states exposed by `useCoreQuery` (use `isError` and `error`) and the `!orgId` case before treating results as an empty list, and compute `campaigns` using `data ?? []` (instead of the current default) so network/permission errors surface to the UI (e.g., surface an error message or fallback) while preserving existing `isLoading` handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/pages/ads/list/AdsListPage.tsx`:
- Around line 25-29: The AdsListPage currently hides query failures by
defaulting campaigns via `data: campaigns = []` and only checking `isLoading`;
update the render logic in `AdsListPage` to first check the query error states
exposed by `useCoreQuery` (use `isError` and `error`) and the `!orgId` case
before treating results as an empty list, and compute `campaigns` using `data ??
[]` (instead of the current default) so network/permission errors surface to the
UI (e.g., surface an error message or fallback) while preserving existing
`isLoading` handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4c946a95-13d9-4d07-9744-8e7019b6df40
📒 Files selected for processing (3)
src/hooks/ads/useCampaignDetail.tssrc/pages/ads/list/AdsListPage.tsxsrc/pages/dashboard/overview/OverviewDashboard.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/pages/dashboard/overview/OverviewDashboard.tsx
- src/hooks/ads/useCampaignDetail.ts
|
P4: 확인했습니다! 수고하셨습니다!! |
🚨 관련 이슈
#113
✨ 변경사항
✏️ 작업 내용
😅 미완성 작업
N/A
📢 논의 사항 및 참고 사항
Summary by CodeRabbit
새로운 기능
개선 사항