[Feature/#162] 플랫폼 대시보드 UI 구현 - #175
Conversation
[Deploy] develop → main 배포 테스트 및 프로덕션 반영
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough플랫폼 대시보드를 전체/개별 뷰로 분리하고, AllPlatformView, SinglePlatformView, PlatformDetailTable 컴포넌트를 추가했습니다. 예산 상태 타입(IBudgetStatus)과 30일 단위 일일 성과 모의데이터(platformDailyPerformanceMock)를 도입해 뷰별 KPI·예산·일별 테이블을 구성하도록 변경했습니다. Changes플랫폼 대시보드 구조 재설계
Sequence DiagramsequenceDiagram
participant User as 사용자
participant Dashboard as PlatformDashboard
participant AllView as AllPlatformView
participant SingleView as SinglePlatformView
participant Table as PlatformDetailTable
participant Mock as MockData
User->>Dashboard: 플랫폼 선택 (전체 or 개별)
alt 전체 선택
Dashboard->>AllView: isLoading 전달
AllView->>Mock: roasRankingMock, adStatusMock, performanceEfficiencyMock 조회
AllView->>AllView: 로딩 여부에 따라 스켈레톤 또는 차트/리스트 렌더링
AllView-->>Dashboard: 전체 뷰 반환
else 개별 선택
Dashboard->>SingleView: platform, isLoading 전달
SingleView->>Mock: performanceEfficiencyMock, budgetStatusMock, platformDailyPerformanceMock 조회
SingleView->>SingleView: useMemo로 KPI·budget 계산, viewRange로 dailyData 슬라이스
SingleView->>Table: dailyData 전달
Table->>Table: useMemo로 합계 계산 후 테이블 렌더링
Table-->>SingleView: 테이블 반환
SingleView-->>Dashboard: 개별 뷰 반환
end
Dashboard-->>User: 렌더된 대시보드 표시
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
src/types/dashboard/platform.ts (1)
53-54: ⚡ Quick win
providerType타입을TPlatformProvider로 좁히는 것을 권장합니다.
string으로 정의하면 같은 파일에 이미 선언된TPlatformProvider유니온 타입의 보호를 받지 못합니다. 잘못된 값(예:"TWITTER")이 할당되어도 컴파일 타임에 잡을 수 없습니다.♻️ 제안 수정
export interface IBudgetStatus { - providerType: string; + providerType: TPlatformProvider; usagePercentage: number; totalBudget: number; totalSpend: number; remainingBudget: number; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/dashboard/platform.ts` around lines 53 - 54, IBudgetStatus currently types providerType as string; narrow it to the existing TPlatformProvider union to get compile-time protection. Update the IBudgetStatus interface so the providerType property uses TPlatformProvider instead of string (change in the IBudgetStatus declaration), ensuring any assignments to providerType are validated against the TPlatformProvider union.src/components/dashboard/platform/AllPlatformView.tsx (1)
17-21: 🏗️ Heavy liftMock 데이터를 컴포넌트 내부에서 직접 임포트하면 실제 API 연동 시 컴포넌트를 수정해야 합니다.
adStatusMock,performanceEfficiencyMock,roasRankingMock를 컴포넌트 안에서 바로 사용하면 다음 문제가 생깁니다:
- 컴포넌트가
@/pages/...경로에 의존하게 되어 의존성 방향이 역전됩니다.- 단위 테스트 시 mock 데이터를 외부에서 주입할 수 없습니다.
- API 연동 시 이 파일을 직접 수정해야 합니다.
데이터를 props 또는 커스텀 훅으로 분리해 컴포넌트는 렌더링만 담당하도록 구조를 개선하는 것을 권장합니다. 가이드라인에 따라 "구조와 책임 분리" 및 "커스텀 훅으로의 분리"가 필요합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/platform/AllPlatformView.tsx` around lines 17 - 21, AllPlatformView currently imports adStatusMock, performanceEfficiencyMock, and roasRankingMock directly which couples the component to test data; change the component to accept these datasets via props or to obtain them from a custom hook (e.g., create usePlatformData that returns { adStatus, performanceEfficiency, roasRanking }) and remove the direct imports from "@/pages/dashboard/platform/platformDashboard.mock"; update the component signature (AllPlatformView) to use the injected props or the hook and adjust callers (pages/tests) to pass the mock data or implement the hook so the component remains purely presentational.src/components/dashboard/platform/PlatformDetailTable.tsx (1)
27-45: ⚡ Quick win
<style>태그로 전역 CSS 클래스를 인라인 주입하면 스타일 충돌이 발생할 수 있습니다.
.custom-scrollbar는 전역 네임스페이스에 추가되므로, 같은 클래스명을 가진 다른 요소에도 영향을 미칩니다. 또한 컴포넌트가 렌더링될 때마다<style>태그가 DOM에 주입됩니다.Tailwind의 arbitrary CSS나 CSS 모듈로 교체하거나, 공통 scrollbar 스타일을 전역 CSS 파일에 정의하는 방식을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/platform/PlatformDetailTable.tsx` around lines 27 - 45, The inline <style> block injecting a global .custom-scrollbar in PlatformDetailTable.tsx creates global CSS and re-injects on each render; remove the inline <style> and instead (a) move the scrollbar rules into a shared global stylesheet if you want app-wide behavior, or (b) create a scoped CSS module or a uniquely-named class (e.g., customScrollbar) and import it in PlatformDetailTable, or (c) use Tailwind arbitrary selectors to apply the scrollbar styles locally; update the element using className="customScrollbar" (or the Tailwind class) instead of relying on the inline <style>.src/pages/dashboard/platform/PlatformDashboard.tsx (1)
32-35:isLoading이 setTimeout 기반으로 시뮬레이션되어 있고, 플랫폼 전환 시 리셋되지 않습니다.현재
useEffect가 마운트 시 한 번만 실행되므로,"전체"→"Google"전환 후에는SinglePlatformView가 항상isLoading=false상태로 렌더링됩니다. API 연동 시에는 React Query의isLoading/isFetching을 기반으로 각 뷰마다 독립적으로 로딩 상태를 관리하는 구조로 교체해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/dashboard/platform/PlatformDashboard.tsx` around lines 32 - 35, The current useEffect creates a simulated isLoading via setTimeout and only runs on mount (useEffect with []), so loading does not reset when platform changes; replace this with per-view loading driven by React Query (useQuery) states: remove the setTimeout/setIsLoading logic in PlatformDashboard, and instead have each platform view (e.g., SinglePlatformView) use its own React Query hook and derive loading from query.isLoading or query.isFetching (or reset local loading when selectedPlatform changes if you must keep a local state); ensure any reference to setIsLoading and the mount-only useEffect is removed and that rendering logic checks the query-provided loading flags to show spinners per platform.src/pages/dashboard/platform/platformDashboard.mock.ts (1)
108-117: ⚡ Quick win
IPlatformDailyPerformance타입이 mock 파일에 정의되어 있어 프로덕션 컴포넌트가 mock 파일에 의존하게 됩니다.
PlatformDetailTable.tsx가@/pages/dashboard/platform/platformDashboard.mock에서 타입을 임포트하고 있는데, 이는 의존 방향이 올바르지 않습니다. 타입은src/types/dashboard/platform.ts로 이동시켜야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/dashboard/platform/platformDashboard.mock.ts` around lines 108 - 117, IPlatformDailyPerformance is defined inside the mock file causing production components to depend on mocks; extract and export the IPlatformDailyPerformance type into a shared dashboard platform types module (create a new module for dashboard/platform types), update PlatformDetailTable.tsx to import the type from that shared module instead of from platformDashboard.mock, and ensure the mock file re-exports or imports the type from the new shared module so all references use the centralized type definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/dashboard/platform/AllPlatformView.tsx`:
- Around line 105-108: The "실시간 트래픽 변화" Card currently renders
TrafficChartSkeleton while isLoading is true but returns null when isLoading is
false, leaving an empty card; update the AllPlatformView component so that the
Card body shows a meaningful placeholder after loading (e.g., a small "No data
yet / TODO: implement chart" message or a Placeholder/EmptyState component)
instead of null—use the existing isLoading and TrafficChartSkeleton symbols to
decide rendering and add a clear placeholder branch for the non-loading case.
In `@src/components/dashboard/platform/PlatformDetailTable.tsx`:
- Around line 106-108: PlatformDetailTable 컴포넌트의 data.map 반복문에서 현재 key={idx}를
사용하고 있어 재정렬 시 React가 DOM을 정확히 재사용하지 못합니다; map 내부의 <tr>에 있는 key를 row.date 같은 각 행의
고유 식별자로 변경하여 key={row.date}를 사용하도록 수정하세요 (참조: data.map(...) 및 해당 <tr> 요소).
- Around line 49-74: In PlatformDetailTable (the thead row rendering the column
headers), add scope="col" to every <th> element in the header row (the date,
비용(지출), 노출 수, 클릭 수, CTR, CPC, 전환 수, ROAS headers) so each header cell is
announced as a column header by assistive technologies; update the <th> tags in
the thead of PlatformDetailTable.tsx to include scope="col" for all header
cells.
- Around line 11-23: The total computed in the useMemo (const total) is
incorrectly using arithmetic means for CTR and CPC; change it to aggregate-based
calculations: sum spend, impressions, clicks, conversions as before, then
compute ctr = totalClicks / totalImpressions (apply ×100 if your app stores CTR
as percent) and compute cpc = totalSpend / totalClicks, guarding both divisions
against zero (return 0 or null when denominator is 0); update the fields in the
returned total object (refer to total, useMemo, and data) instead of averaging
per-row ctr/cpc.
In `@src/components/dashboard/platform/SinglePlatformView.tsx`:
- Around line 138-140: The button rendering in SinglePlatformView (the button
containing AiButtonSvg) lacks an accessible name; add an accessible label by
adding an aria-label (e.g., aria-label="Summarize with AI") or include hidden
descriptive text inside the button so screen readers can announce its purpose;
ensure the label is concise and matches the button action and keep AiButtonSvg
as the visible icon.
- Around line 156-162: The map is forcing trend to any (kpi.trend as any) which
violates the "avoid any" guideline; instead update the kpis type or the mapping
so StatCard receives a correctly typed trend: either declare the kpis array with
an explicit type that defines trend.direction as the correct literal union
(e.g., 'up'|'down'|...) or coerce the direction at the call site with a safe
type assertion/transform (e.g., map direction via a conditional to the literal
union) and remove the use of as any; locate the kpis variable and the StatCard
call in SinglePlatformView (the kpis.map(...) block and the StatCard prop trend)
and apply the type fix so StatCard's trend prop receives a properly typed object
instead of any.
- Around line 175-208: The budget card renders BudgetGaugeChart immediately
instead of showing a loading skeleton; update the Card children to check the
existing isLoading flag (or add one if missing) and render a skeleton
placeholder (e.g., a div with skeleton classes or a Spinner component) while
isLoading is true, otherwise render the current budget conditional (use budget ?
<BudgetGaugeChart {...budget} /> : “데이터가 없습니다.”). Ensure the RightElement logic
for budgetStatus remains unchanged and only show the Badge when not loading.
In `@src/pages/dashboard/platform/platformDashboard.mock.ts`:
- Around line 160-162: The CTR and CPC calculations can produce NaN/Infinity
when impressions or clicks are zero; update the expressions in the mock data
where ctr and cpc are computed (the ctr assignment and the cpc: Math.floor(spend
/ clicks) expression) to guard against division by zero by returning a safe
default (e.g., 0 or null) when impressions or clicks === 0; implement the check
inline so ctr uses impressions > 0 ? Number(((clicks / impressions) *
100).toFixed(2)) : 0 and cpc uses clicks > 0 ? Math.floor(spend / clicks) : 0
(or another chosen safe default).
- Line 129: The variable today is hardcoded to new Date(2026, 4, 5) which
freezes the mock 30-day window; change the initialization of today in
platformDashboard.mock.ts (the today const) to use new Date() so generated data
is based on the current date (adjust or normalize only if other code expects
midnight/UTC).
---
Nitpick comments:
In `@src/components/dashboard/platform/AllPlatformView.tsx`:
- Around line 17-21: AllPlatformView currently imports adStatusMock,
performanceEfficiencyMock, and roasRankingMock directly which couples the
component to test data; change the component to accept these datasets via props
or to obtain them from a custom hook (e.g., create usePlatformData that returns
{ adStatus, performanceEfficiency, roasRanking }) and remove the direct imports
from "@/pages/dashboard/platform/platformDashboard.mock"; update the component
signature (AllPlatformView) to use the injected props or the hook and adjust
callers (pages/tests) to pass the mock data or implement the hook so the
component remains purely presentational.
In `@src/components/dashboard/platform/PlatformDetailTable.tsx`:
- Around line 27-45: The inline <style> block injecting a global
.custom-scrollbar in PlatformDetailTable.tsx creates global CSS and re-injects
on each render; remove the inline <style> and instead (a) move the scrollbar
rules into a shared global stylesheet if you want app-wide behavior, or (b)
create a scoped CSS module or a uniquely-named class (e.g., customScrollbar) and
import it in PlatformDetailTable, or (c) use Tailwind arbitrary selectors to
apply the scrollbar styles locally; update the element using
className="customScrollbar" (or the Tailwind class) instead of relying on the
inline <style>.
In `@src/pages/dashboard/platform/platformDashboard.mock.ts`:
- Around line 108-117: IPlatformDailyPerformance is defined inside the mock file
causing production components to depend on mocks; extract and export the
IPlatformDailyPerformance type into a shared dashboard platform types module
(create a new module for dashboard/platform types), update
PlatformDetailTable.tsx to import the type from that shared module instead of
from platformDashboard.mock, and ensure the mock file re-exports or imports the
type from the new shared module so all references use the centralized type
definition.
In `@src/pages/dashboard/platform/PlatformDashboard.tsx`:
- Around line 32-35: The current useEffect creates a simulated isLoading via
setTimeout and only runs on mount (useEffect with []), so loading does not reset
when platform changes; replace this with per-view loading driven by React Query
(useQuery) states: remove the setTimeout/setIsLoading logic in
PlatformDashboard, and instead have each platform view (e.g.,
SinglePlatformView) use its own React Query hook and derive loading from
query.isLoading or query.isFetching (or reset local loading when
selectedPlatform changes if you must keep a local state); ensure any reference
to setIsLoading and the mount-only useEffect is removed and that rendering logic
checks the query-provided loading flags to show spinners per platform.
In `@src/types/dashboard/platform.ts`:
- Around line 53-54: IBudgetStatus currently types providerType as string;
narrow it to the existing TPlatformProvider union to get compile-time
protection. Update the IBudgetStatus interface so the providerType property uses
TPlatformProvider instead of string (change in the IBudgetStatus declaration),
ensuring any assignments to providerType are validated against the
TPlatformProvider union.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 98085464-44b6-4766-af92-2573a59bca6f
⛔ Files ignored due to path filters (2)
src/assets/logo/social-logo/wordmark/meta-wordmark.svgis excluded by!**/*.svgand included bysrc/**src/assets/logo/social-logo/wordmark/naver-wordmark.svgis excluded by!**/*.svgand included bysrc/**
📒 Files selected for processing (6)
src/components/dashboard/platform/AllPlatformView.tsxsrc/components/dashboard/platform/PlatformDetailTable.tsxsrc/components/dashboard/platform/SinglePlatformView.tsxsrc/pages/dashboard/platform/PlatformDashboard.tsxsrc/pages/dashboard/platform/platformDashboard.mock.tssrc/types/dashboard/platform.ts
📚 Storybook 배포 완료
|
[Deploy] develop → main 배포 테스트 및 프로덕션 반영
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/dashboard/platform/PlatformDetailTable.tsx (1)
3-3: ⚡ Quick win컴포넌트가 mock 모듈 타입에 직접 결합돼 있어요.
PlatformDetailTable이platformDashboard.mock.ts에 의존하면 실제 API 타입으로 전환할 때 UI 컴포넌트까지 함께 수정해야 합니다. 공용 타입(types/models)으로 분리해서 import 경계를 끊는 게 안전합니다.As per coding guidelines
src/**: "구조와 책임 분리: 페이지에 비즈니스 로직이 과도하지 않은지 확인. 커스텀 훅으로의 분리 여부 검토."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/platform/PlatformDetailTable.tsx` at line 3, PlatformDetailTable is directly importing IPlatformDailyPerformance from platformDashboard.mock, coupling the UI to a mock module; extract the type into a shared types/models module (e.g., create a types or models export like IPlatformDailyPerformance in src/types/platform.ts or src/models/platform.ts), update PlatformDetailTable to import the type from that new shared module, and update the mock file to import the type from the shared module as well so the mock and component both reference the common type instead of the mock file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/dashboard/platform/PlatformDetailTable.tsx`:
- Line 94: Replace the table cell that renders the "합계" label from a data cell
to a row header by changing the element in PlatformDetailTable (the cell
currently rendering <td className="px-4 py-5 border-b
border-bg-disabled">합계</td>) to a header cell with scope="row" (preserve the
className and other attributes) so screen readers treat it as a row header;
update any associated tests or snapshots that assert table cell markup if
present.
---
Nitpick comments:
In `@src/components/dashboard/platform/PlatformDetailTable.tsx`:
- Line 3: PlatformDetailTable is directly importing IPlatformDailyPerformance
from platformDashboard.mock, coupling the UI to a mock module; extract the type
into a shared types/models module (e.g., create a types or models export like
IPlatformDailyPerformance in src/types/platform.ts or src/models/platform.ts),
update PlatformDetailTable to import the type from that new shared module, and
update the mock file to import the type from the shared module as well so the
mock and component both reference the common type instead of the mock file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5c1dc478-fa0b-4f59-b63d-901a3c463aad
📒 Files selected for processing (3)
src/components/dashboard/platform/PlatformDetailTable.tsxsrc/components/dashboard/platform/SinglePlatformView.tsxsrc/components/sidebar/Sidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/dashboard/platform/SinglePlatformView.tsx
jjjsun
left a comment
There was a problem hiding this comment.
P4: 확인했습니다! 통합대시보드에서 스냅샷 부분이 추가되어서 그부분도 추가관련해서 한번 고려하면 좋을것같아요!
🚨 관련 이슈
✨ 변경사항
✏️ 작업 내용
😅 미완성 작업
📢 논의 사항 및 참고 사항
다음 PR에서 실시간 트래픽 변화 구현 후 플랫폼 대시보드에 적용 예정입니다!
Summary by CodeRabbit
새로운 기능
Style