Skip to content

[Feature/#209] AI 요약하기 디자인 구조 변경#223

Merged
Seojegyeong merged 14 commits into
developfrom
feature/#209
May 19, 2026
Merged

[Feature/#209] AI 요약하기 디자인 구조 변경#223
Seojegyeong merged 14 commits into
developfrom
feature/#209

Conversation

@Seojegyeong
Copy link
Copy Markdown
Collaborator

@Seojegyeong Seojegyeong commented May 18, 2026

#209

🚨 관련 이슈

✨ 변경사항

  • 🐞 BugFix Something isn't working
  • 💻 CrossBrowsing Browser compatibility
  • 🌏 Deploy Deploy
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (storybook, jest, etc.)

✏️ 작업 내용

  • AI 요약을 별도 드로어 진입이 아니라, 통합 대시보드 본문의 인라인 카드로 변경
  • UX 포인트
    • 접힘: 제목 + AI 뱃지 + 안내 문구(“펼치기를 누르면…”)
    • 펼침: 기존 드로어 리포트와 유사한 구조를 카드 안에 인라인 표시
  • OverviewAiDrawer 및 관련 상태(isAiPanelOpen) 제거 + 헤더 「AI 요약하기」 버튼 제거 진행 완료
  • overview 전용은 해당 page/ 안에 sections/으로 구성

파일구조

src/
├── components/dashboard/ai-report/     # 공용 (통합·플랫폼 대시보드)
│   ├── components/
│   │   └── AiSummaryCard.tsx
│   ├── utils/
│   │   └── aiReport.utils.ts
│   └── print/
│       ├── AiSummaryPrintReport.tsx
│       ├── downloadAiSummaryPdf.ts
│       └── printAssets.ts
│
├── pages/dashboard/overview/             # 통합 대시보드 전용
│   ├── OverviewDashboard.tsx
│   ├── mock/
│   │   └── aiReport.mock.ts
│   └── sections/
│       └── ...
│
└── types/dashboard/
    ├── aiReport.ts                     # IAiReportResponse 등
    └── overview.ts                       # re-export


스크린샷

image image

저장되는 report

whereyouad-report.pdf

😅 미완성 작업

  • API 연동 미완성

📢 논의 사항 및 참고 사항

  • 디자인 피드백 부탁드립니다!
  • 추가로 이런 형태가 인라인 카드 형태로 확정되면 @dnd-kit으로 KPI·예산·플랫폼·AI 카드 등 순서 변경·재배치가 필요할 것 같습니다.
    (@dnd-kit - 통합 대시보드 카드 순서를 사용자가 직접 변경할 수 있게 합니다. 대시보드 카드 커스텀)
  • 링크 복사하기는 불필요한 요소라고 판단되어 pdf저장하기만 따로 추가했습니다!

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • AI 요약 보고서 PDF 다운로드 기능 추가
  • 개선 사항

    • AI 요약 UI를 드로어 패널에서 대시보드 카드로 변경하여 더 빠르게 접근 가능
    • 대시보드 로딩 화면에 스켈레톤 UI 추가로 개선된 로딩 경험 제공
    • 타이포그래피 및 인쇄/PDF 스타일 최적화

Review Change Stack

@Seojegyeong Seojegyeong requested review from YermIm and jjjsun May 18, 2026 05:55
@Seojegyeong Seojegyeong self-assigned this May 18, 2026
@Seojegyeong Seojegyeong added the 🎨 Html&css 마크업 & 스타일링 label May 18, 2026
@Seojegyeong Seojegyeong linked an issue May 18, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

대시보드 오버뷰의 AI 요약 기능을 드로어 패널에서 카드형 UI로 전환합니다. 데이터 모델, 공유 스켈레톤, 카드 컴포넌트, 인쇄/PDF 기능을 추가하고 대시보드에 통합한 뒤, 섹션별 로딩 상태를 통일하며 스타일 토큰을 확장합니다.

Changes

AI 요약 카드 기능 구현 및 대시보드 통합

Layer / File(s) Summary
AI 리포트 데이터 모델
src/types/dashboard/overview.ts
섹션, 핵심 지표, 전체 응답의 IAiReportSection, IAiReportKeyMetric, IAiReportResponse 인터페이스를 정의하여 카드 기능의 데이터 구조를 제공합니다.
공유 스켈레톤 컴포넌트
src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx
KPI, 트래픽 차트, 예산 게이지, 플랫폼 랭킹, 캠페인 스냅샷의 로딩 UI를 위한 5개 스켈레톤 컴포넌트를 추가하여 각 섹션의 레이아웃을 반영합니다.
AI 요약 카드 UI 컴포넌트
src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx
문단 렌더링, 하이라이트 섹션, 보고서 바디, 접기/펼치기 토글, framer-motion 애니메이션, 접근성 속성을 조합하여 완전한 카드 UI를 구현합니다. useReducedMotion으로 모션 선호를 반영합니다.
AI 리포트 모의 데이터 및 변환 유틸
src/pages/dashboard/overview/ai-summary/aiReport.mock.ts, src/pages/dashboard/overview/ai-summary/aiReport.utils.ts
모의 데이터를 타입으로 선언하고, 인쇄/PDF용 문서 구조로 변환하는 유틸을 제공합니다. 섹션별 제목, 문단 분리, 실행 요약 생성, 한국어 날짜 포맷 함수를 포함합니다.
PDF 다운로드 및 인쇄 컴포넌트
src/pages/dashboard/overview/ai-summary/downloadAiSummaryPdf.ts, src/pages/dashboard/overview/ai-summary/print/OverviewAiSummaryPrintReport.tsx
숨김 iframe으로 인쇄용 HTML을 생성하고 브라우저 인쇄를 트리거합니다. 표지, 본문, 푸터 구조로 렌더링하며 폰트 임베딩과 페이지 레이아웃을 제어합니다.
대시보드 페이지 AI 카드 통합
src/pages/dashboard/overview/OverviewDashboard.tsx, src/pages/dashboard/platform/PlatformDashboard.tsx
기존 OverviewAiDrawer 및 AI 패널 상태/핸들러를 제거하고 OverviewAiSummaryCard를 렌더 트리에 추가합니다. 헤더 버튼 설정도 정리합니다.
인쇄용 스타일과 디자인 토큰 확장
src/styles/aiReport.print.css, src/styles/print.css, src/styles/tokens.css, src/styles/utilities.css, src/stories/Typography.stories.tsx
인쇄/PDF용 CSS를 추가하고 Body 3 타이포그래피 토큰 3개와 유틸 클래스, 스토리를 확장합니다. 인쇄 포털 구조를 재구성합니다.
각 섹션 로딩 상태 공유 스켈레톤으로 교체
src/pages/dashboard/overview/sections/OverviewKpiSection.tsx, src/pages/dashboard/overview/sections/OverviewBudgetSection.tsx, src/pages/dashboard/overview/sections/OverviewPlatformSection.tsx
KPI, 예산, 플랫폼 섹션에서 로컬 스켈레톤을 제거하고 공유 컴포넌트로 통일합니다.
캠페인 카드 플랫폼 표기 로직 개선
src/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsx
플랫폼 값을 TProviderType 화이트리스트로 검증하고 PLATFORM_MAP에서 조회하는 providerDisplayName() 함수를 추가하여 안정성을 높입니다. 스켈레톤도 공유 컴포넌트로 교체합니다.

Sequence Diagram

sequenceDiagram
  participant User
  participant OverviewDashboard
  participant OverviewAiSummaryCard
  participant AiReportBody
  participant downloadAiSummaryPdf
  participant PrintReport
  participant Browser as Browser Print Dialog
  
  User->>OverviewDashboard: 대시보드 접속
  OverviewDashboard->>OverviewAiSummaryCard: 카드 렌더링
  OverviewAiSummaryCard->>User: 요약 하이라이트 표시
  
  alt 사용자가 펼치기 클릭
    User->>OverviewAiSummaryCard: 토글 버튼 클릭
    OverviewAiSummaryCard->>AiReportBody: AnimatePresence 트리거
    AiReportBody->>User: 전략/경고/인사이트 섹션 애니메이션 표시
  end
  
  alt 사용자가 PDF 다운로드 클릭
    User->>OverviewAiSummaryCard: PDF 다운로드 버튼 클릭
    OverviewAiSummaryCard->>downloadAiSummaryPdf: TAiReportPrintDocument 전달
    downloadAiSummaryPdf->>PrintReport: 정적 마크업 렌더링
    PrintReport->>downloadAiSummaryPdf: HTML 반환
    downloadAiSummaryPdf->>Browser: 숨김 iframe 주입 및 print() 호출
    Browser->>User: 인쇄 대화 표시
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • jjjsun
  • YermIm
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 AI 요약 UI를 드로어에서 인라인 카드로 변경한 내용을 명확하게 반영하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명이 대부분 작성되어 있으나, 필수 섹션 중 일부가 미흡합니다. 변경사항 체크박스는 반영되었고 작업 내용도 명확하지만, 미완성 작업 섹션이 간략하고 API 연동 상태가 불충분합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#209

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-spxfietxil.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=301

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/types/dashboard/overview.ts (1)

56-74: 💤 Low value

전략 제안과 주의사항 타입 구조 중복

strategySuggestionwarning 필드가 동일한 { title: string; content: string } 구조를 가지고 있어요. 의미적으로 분리하려는 의도라면 괜찮지만, 공통 타입으로 추출하면 유지보수가 더 쉬울 수 있습니다.

♻️ 선택적 리팩토링 제안
+// AI 요약 리포트 섹션 (공통)
+export interface IAiReportContentBlock {
+  title: string;
+  content: string;
+}
+
 // AI 요약 리포트 응답
-export interface IAiReportSection {
-  title: string;
-  content: string;
-}
+export type IAiReportSection = IAiReportContentBlock;

 export interface IAiReportResponse {
   label: string;
   title: string;
-  strategySuggestion: {
-    title: string;
-    content: string;
-  };
+  strategySuggestion: IAiReportContentBlock;
   sections: IAiReportSection[];
-  warning: {
-    title: string;
-    content: string;
-  };
+  warning: IAiReportContentBlock;
 }
🤖 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/overview.ts` around lines 56 - 74, Extract the duplicated
{ title: string; content: string } shape into a shared interface (e.g.,
ITextBlock or ITitleContent) and replace the inline types used by
IAiReportResponse.strategySuggestion and IAiReportResponse.warning with that new
interface; update IAiReportSection if appropriate to reuse the same interface
(references: IAiReportResponse, strategySuggestion, warning, IAiReportSection)
so the common structure is defined once for easier maintenance.
src/pages/dashboard/overview/OverviewAiSummaryCard.tsx (2)

256-267: 💤 Low value

토글 핸들러 구조 단순화 가능

handleOpen, handleCollapse, handleToggle 세 개의 콜백으로 분리되어 있는데, handleToggle 하나로 충분해 보여요. 현재 handleOpenhandleCollapse를 별도로 사용하는 곳이 없다면 제거해도 됩니다.

♻️ 단순화 제안
  const prefersReducedMotion = useReducedMotion();
  const [isExpanded, setIsExpanded] = useState(false);

- const handleOpen = useCallback(() => {
-   setIsExpanded(true);
- }, []);
-
- const handleCollapse = useCallback(() => {
-   setIsExpanded(false);
- }, []);
-
- const handleToggle = useCallback(() => {
-   if (isExpanded) handleCollapse();
-   else handleOpen();
- }, [handleCollapse, handleOpen, isExpanded]);
+ const handleToggle = useCallback(() => {
+   setIsExpanded((prev) => !prev);
+ }, []);
🤖 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/overview/OverviewAiSummaryCard.tsx` around lines 256 -
267, The code currently defines three callbacks (handleOpen, handleCollapse,
handleToggle) but only handleToggle is used; simplify by removing handleOpen and
handleCollapse and replace handleToggle with a single useCallback that toggles
state via setIsExpanded(prev => !prev) (update its dependency array to
[setIsExpanded] or [] if setState is stable), and update any references to
handleOpen/handleCollapse to use handleToggle; keep function name handleToggle
and ensure it's exported/used wherever the prior handlers were referenced.

311-327: ⚡ Quick win

layout prop 성능 영향 검토 필요

Line 313에서 motion.divlayout prop을 사용하고 있는데, 이미 height를 명시적으로 애니메이션하고 있어요 (line 319, 322). layout prop은 매 프레임마다 레이아웃 재계산을 유발할 수 있어서, 명시적인 height 애니메이션만으로 충분하다면 제거하는 게 성능상 유리합니다.

🔍 확인 방법

layout prop을 제거해보고 애니메이션이 정상 동작하는지 테스트해보세요:

  <motion.div
    key="ai-summary-panel"
-   layout
    initial={

만약 layout prop이 특정 이유로 필요하다면 (예: 내부 콘텐츠 크기 변화 대응), 주석으로 이유를 남겨두면 좋겠어요.

🤖 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/overview/OverviewAiSummaryCard.tsx` around lines 311 -
327, The motion.div with key "ai-summary-panel" is using the layout prop while
also animating explicit height in initial/animate/exit (uses
prefersReducedMotion, panelTransition, panelExitTransition); remove the layout
prop from the motion.div to avoid forced layout recalculations and verify the
height/opacity animations still behave correctly, and if you determine layout is
necessary for internal content resizing, leave it but add a comment on why
layout is required (or consider replacing layout with an explicit measurement
strategy).
🤖 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/pages/dashboard/overview/OverviewAiSummaryCard.tsx`:
- Line 9: OverviewAiSummaryCard currently imports aiReportMockData directly
which couples the component to test data; remove the import and make the
component accept the report data via props (e.g., add a prop like aiReport:
AiReportType) or call a new hook (e.g., useAiReport) that returns { data,
isLoading } so the component no longer depends on aiReportMockData; update the
component signature (OverviewAiSummaryCard) to use the prop or hook and adjust
any callers to pass the mock (or switch to the hook) and create
hooks/dashboard/useAiReport.ts if using the hook approach to encapsulate
fetching.

---

Nitpick comments:
In `@src/pages/dashboard/overview/OverviewAiSummaryCard.tsx`:
- Around line 256-267: The code currently defines three callbacks (handleOpen,
handleCollapse, handleToggle) but only handleToggle is used; simplify by
removing handleOpen and handleCollapse and replace handleToggle with a single
useCallback that toggles state via setIsExpanded(prev => !prev) (update its
dependency array to [setIsExpanded] or [] if setState is stable), and update any
references to handleOpen/handleCollapse to use handleToggle; keep function name
handleToggle and ensure it's exported/used wherever the prior handlers were
referenced.
- Around line 311-327: The motion.div with key "ai-summary-panel" is using the
layout prop while also animating explicit height in initial/animate/exit (uses
prefersReducedMotion, panelTransition, panelExitTransition); remove the layout
prop from the motion.div to avoid forced layout recalculations and verify the
height/opacity animations still behave correctly, and if you determine layout is
necessary for internal content resizing, leave it but add a comment on why
layout is required (or consider replacing layout with an explicit measurement
strategy).

In `@src/types/dashboard/overview.ts`:
- Around line 56-74: Extract the duplicated { title: string; content: string }
shape into a shared interface (e.g., ITextBlock or ITitleContent) and replace
the inline types used by IAiReportResponse.strategySuggestion and
IAiReportResponse.warning with that new interface; update IAiReportSection if
appropriate to reuse the same interface (references: IAiReportResponse,
strategySuggestion, warning, IAiReportSection) so the common structure is
defined once for easier maintenance.
🪄 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: 6d61dbea-a7a1-4f30-8a1d-aa9a706dc503

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd77f7 and 7a73cf3.

⛔ Files ignored due to path filters (1)
  • src/assets/icon/common/lightbulb.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (5)
  • src/pages/dashboard/overview/OverviewAiSummaryCard.tsx
  • src/pages/dashboard/overview/OverviewCampaignSnapshotCard.tsx
  • src/pages/dashboard/overview/OverviewDashboard.tsx
  • src/pages/dashboard/overview/aiReport.mock.ts
  • src/types/dashboard/overview.ts

Comment thread src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx Outdated
@YermIm
Copy link
Copy Markdown
Collaborator

YermIm commented May 18, 2026

P4: UI 깔끔한 것 같습니다! 혹시 링크 공유, 저장 기능은 제외하는 것일까요?!

@Seojegyeong
Copy link
Copy Markdown
Collaborator Author

P4: UI 깔끔한 것 같습니다! 혹시 링크 공유, 저장 기능은 제외하는 것일까요?!

헉! 펼쳤을 때 오른쪽 상단에 추가하겠습니다!

@jjjsun
Copy link
Copy Markdown
Collaborator

jjjsun commented May 19, 2026

image P3: 요약하기 부분 텍스트가 작은것같습니다. 한단계씩 올려보았는데 이렇게 수정진행하면좋을것같습니다. p태그와 넘버는 font-body1, 제모은 font-heading4로 수정하면 더 잘 보일것같습니다.

@jjjsun
Copy link
Copy Markdown
Collaborator

jjjsun commented May 19, 2026

P3: 지금 AI성과요약 카드 열면 애니메이션으로 부드럽게 열리는데, 혹시 카드안에 '전략제안'과 '주의가 필요한 부분'도 더보기 눌렀을때 애니메이션으로 부드럽게 열리도록 똑같이 하면 어떨까요?

@Seojegyeong
Copy link
Copy Markdown
Collaborator Author

Seojegyeong commented May 19, 2026

P3: 지금 AI성과요약 카드 열면 애니메이션으로 부드럽게 열리는데, 혹시 카드안에 '전략제안'과 '주의가 필요한 부분'도 더보기 눌렀을때 애니메이션으로 부드럽게 열리도록 똑같이 하면 어떨까요?

더보기가 굳이 불필요한 요소인 것 같아서 제거하려고 합니다! 텍스트 크기는 전체적으로 크게 수정하겠습니다. 카드 제목보다는 작게 유지해야 위계가 드러날 것 같아서 맞춰서 수정하겠습니다!

@Seojegyeong
Copy link
Copy Markdown
Collaborator Author

P3: 지금 AI성과요약 카드 열면 애니메이션으로 부드럽게 열리는데, 혹시 카드안에 '전략제안'과 '주의가 필요한 부분'도 더보기 눌렀을때 애니메이션으로 부드럽게 열리도록 똑같이 하면 어떨까요?

더보기가 굳이 불필요한 요소인 것 같아서 제거하려고 합니다! 텍스트 크기는 전체적으로 크게 수정하겠습니다. 카드 제목보다는 작게 유지해야 위계가 보일 것 같아서 맞춰서 수정하겠습니다!

@jjjsun
Copy link
Copy Markdown
Collaborator

jjjsun commented May 19, 2026

P3: 지금 AI성과요약 카드 열면 애니메이션으로 부드럽게 열리는데, 혹시 카드안에 '전략제안'과 '주의가 필요한 부분'도 더보기 눌렀을때 애니메이션으로 부드럽게 열리도록 똑같이 하면 어떨까요?

더보기가 굳이 불필요한 요소인 것 같아서 제거하려고 합니다! 텍스트 크기는 전체적으로 크게 수정하겠습니다. 카드 제목보다는 작게 유지해야 위계가 드러날 것 같아서 맞춰서 수정하겠습니다!

좋습니다! 그러면 더보기 없이 바로 텍스트가 다나오도록 변경되는걸까요?

@Seojegyeong
Copy link
Copy Markdown
Collaborator Author

P3: 지금 AI성과요약 카드 열면 애니메이션으로 부드럽게 열리는데, 혹시 카드안에 '전략제안'과 '주의가 필요한 부분'도 더보기 눌렀을때 애니메이션으로 부드럽게 열리도록 똑같이 하면 어떨까요?

더보기가 굳이 불필요한 요소인 것 같아서 제거하려고 합니다! 텍스트 크기는 전체적으로 크게 수정하겠습니다. 카드 제목보다는 작게 유지해야 위계가 드러날 것 같아서 맞춰서 수정하겠습니다!

좋습니다! 그러면 더보기 없이 바로 텍스트가 다나오도록 변경되는걸까요?

넵 맞습니다!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx (1)

10-10: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Mock 데이터 직접 의존성 제거 필요

이전 리뷰에서 지적된 내용과 동일하게, 컴포넌트가 aiReportMockData를 직접 import해서 사용하고 있어요. 이 구조는 컴포넌트 재사용성을 떨어뜨리고 실제 API 연동 시 수정 범위가 커집니다.

데이터를 props로 받거나 커스텀 훅(useAiReport 등)에서 fetch하도록 분리하는 것을 권장해요.

🤖 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/overview/ai-summary/OverviewAiSummaryCard.tsx` at line
10, OverviewAiSummaryCard currently imports aiReportMockData directly; remove
this hard dependency by making the component accept the data via props or by
delegating fetch to a custom hook. Update the OverviewAiSummaryCard component
signature to accept a prop (e.g., aiReport: AiReportType) and replace all uses
of aiReportMockData with that prop, or create/use a hook named useAiReport that
returns the data and loading/error states and call it from the parent (not the
component) if you want composition; ensure PropTypes/TypeScript types
(AiReportType) and default/optional handling are added where referenced in
OverviewAiSummaryCard so the component no longer imports aiReportMockData.
🧹 Nitpick comments (6)
src/pages/dashboard/overview/sections/OverviewBudgetSection.tsx (1)

14-16: 💤 Low value

import 경로 스타일 일관성

Line 14는 절대 경로(@/components/...)를, Line 16은 상대 경로(./OverviewCampaignSnapshotCard)를 사용하고 있습니다.

같은 파일 내에서 import 경로 스타일이 혼재되어 있는데, 일관성을 위해 한 가지 스타일로 통일하는 것이 좋습니다. 일반적으로:

  • 같은 디렉토리나 형제 디렉토리의 파일은 상대 경로
  • 다른 도메인/공통 컴포넌트는 절대 경로

를 사용하는 것이 관례입니다. 현재 구조에서는 둘 다 타당하지만, 프로젝트의 import 컨벤션을 확인하고 따르는 것을 권장합니다.

🤖 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/overview/sections/OverviewBudgetSection.tsx` around lines
14 - 16, Imports in this file mix absolute and relative styles:
OverviewBudgetGaugeSkeleton is imported via "`@/components/`..." while
OverviewCampaignSnapshotCard uses a relative "./" path; pick the project's
convention and make them consistent (prefer using relative imports for
sibling/nearby components like OverviewCampaignSnapshotCard and any local
skeletons), update the import for OverviewBudgetGaugeSkeleton or
OverviewCampaignSnapshotCard so both use the same style and ensure the module
resolution still works for the chosen form.
src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx (2)

5-5: 💤 Low value

외부 모듈 의존성 검토 필요

PLATFORM_ROAS_TABLE_COLPlatformRoasTable에서 직접 import하고 있는데, 스켈레톤 컴포넌트가 실제 데이터 컴포넌트의 내부 구현 상수에 의존하는 구조입니다.

향후 PlatformRoasTable의 그리드 레이아웃이 변경되면 이 스켈레톤도 함께 수정해야 하므로, 두 컴포넌트 간 결합도가 높아집니다. 가능하다면 공통 상수 파일로 분리하거나, 스켈레톤 컴포넌트 내부에서 동일한 그리드 정의를 복제하는 것을 고려해보세요.

🤖 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/overview/skeleton/OverviewSkeleton.tsx` at line 5,
OverviewSkeleton currently imports PLATFORM_ROAS_TABLE_COL from
PlatformRoasTable, creating tight coupling; either extract the grid constant
into a shared module (e.g., create and export PLATFORM_ROAS_TABLE_COL from a new
shared/constants or layout file and update both PlatformRoasTable and
OverviewSkeleton to import it) or duplicate the grid definition inside
OverviewSkeleton so it no longer imports PlatformRoasTable directly; update
imports in PlatformRoasTable and OverviewSkeleton to reference the new shared
export if you choose extraction.

8-16: ⚡ Quick win

접근성 개선 제안

스켈레톤 컴포넌트들에 스크린 리더를 위한 로딩 상태 표시가 없습니다. 시각적으로는 로딩 중임을 알 수 있지만, 스크린 리더 사용자에게는 현재 상태가 전달되지 않습니다.

각 스켈레톤 컴포넌트의 최상위 요소에 role="status"aria-live="polite" 또는 aria-busy="true"와 시각적으로 숨겨진 "데이터 로딩 중" 텍스트를 추가하는 것을 권장합니다.

♿ 접근성 개선 예시
 export function OverviewKpiCardSkeleton() {
   return (
-    <div className="flex flex-col gap-3 rounded-3xl border border-surface-100/40 bg-surface-100/80 px-7 py-5 shadow-card backdrop-blur-sm">
+    <div 
+      className="flex flex-col gap-3 rounded-3xl border border-surface-100/40 bg-surface-100/80 px-7 py-5 shadow-card backdrop-blur-sm"
+      role="status"
+      aria-label="KPI 카드 로딩 중"
+    >
       <Skeleton className="h-4 w-16" />
       <Skeleton className="h-6 w-24" />
       <Skeleton className="h-6 w-14 rounded-full" />
     </div>
   );
 }

다른 스켈레톤 컴포넌트들도 동일하게 적용할 수 있습니다.

Also applies to: 19-21, 24-54, 57-89, 92-99

🤖 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/overview/skeleton/OverviewSkeleton.tsx` around lines
8 - 16, The OverviewKpiCardSkeleton currently has no accessible loading
semantics; update the top-level div in OverviewKpiCardSkeleton to include
role="status" and either aria-live="polite" or aria-busy="true" and add a
visually hidden text node (e.g., a span with screen-reader-only CSS content like
"데이터 로딩 중") so screen readers announce the loading state; apply the same pattern
to the other skeleton components in this file (the other top-level skeleton
wrappers referenced in the diff ranges) to ensure consistent accessible loading
indicators.
src/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsx (1)

24-28: ⚡ Quick win

PLATFORM_MAP 타입 선언을 개선하면 타입 안정성이 향상됩니다.

현재 PLATFORM_MAPRecord<string, string>으로 선언되어 있어서, TypeScript 입장에서는 PLATFORM_MAP[key]string | undefined를 반환할 수 있다고 봅니다. 함수의 반환 타입은 string | null이므로 타입 계약이 정확하지 않습니다.

다만 라인 39의 타입 가드 .filter((name): name is string => name != null)가 undefined를 명시적으로 걸러내기 때문에 런타임에서는 안전합니다.

🛡️ 제안: PLATFORM_MAP 타입을 보다 명확하게
- export const PLATFORM_MAP: Record<string, string> = {
+ export const PLATFORM_MAP: Record<TProviderType, string> = {
    GOOGLE: "Google",
    NAVER: "NAVER",
    META: "Meta",
  };

또는 함수 내에서 안전하게 처리:

  function providerDisplayName(raw: string): string | null {
    const key = raw.toUpperCase() as TProviderType;
    if (!PROVIDERS.includes(key)) return null;
-   return PLATFORM_MAP[key];
+   const displayName = PLATFORM_MAP[key];
+   return displayName ?? null;
  }
🤖 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/overview/sections/OverviewCampaignSnapshotCard.tsx`
around lines 24 - 28, PLATFORM_MAP is typed too loosely (Record<string,string>)
so PLATFORM_MAP[key] is string | undefined; tighten its type to either
Record<TProviderType, string> if every TProviderType is present, or
Partial<Record<TProviderType, string>> if some keys may be missing, and update
providerDisplayName to handle undefined explicitly (e.g., look up
PLATFORM_MAP[key] and return it or null) so the return type string | null is
honored; reference symbols: PLATFORM_MAP, providerDisplayName, TProviderType,
PROVIDERS.
src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx (1)

260-276: 💤 Low value

layout prop 사용 검토

motion.divlayout prop이 설정되어 있는데, 이 prop은 모든 자식 요소의 레이아웃 변경을 추적하므로 성능 비용이 있을 수 있어요.

현재 애니메이션은 heightopacity만 변경하고 있으므로, layout prop 없이도 동일한 효과를 낼 수 있습니다. 복잡한 레이아웃 재배치가 필요한 경우가 아니라면 제거를 고려해 보세요.

♻️ 제안하는 수정
              <motion.div
                key="ai-summary-panel"
-               layout
                initial={
                  prefersReducedMotion
                    ? { opacity: 1, height: "auto" }
                    : { opacity: 0, height: 0 }
                }
🤖 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/overview/ai-summary/OverviewAiSummaryCard.tsx` around
lines 260 - 276, The motion.div with key "ai-summary-panel" currently includes
the layout prop which forces Framer Motion to track children layout changes and
can hurt performance; remove the layout prop from that motion.div (while keeping
initial, animate, exit, transition, panelExitTransition, panelTransition and the
prefersReducedMotion handling) so the component continues animating height and
opacity without the extra layout tracking; verify the visual behavior still
matches expectations after removal.
src/pages/dashboard/overview/ai-summary/downloadAiSummaryPdf.ts (1)

51-56: ⚡ Quick win

afterprint 미발생 케이스 대비 정리(fallback cleanup)를 추가해 주세요.

현재는 afterprint 이벤트에만 정리가 걸려 있어, 이벤트가 누락되는 환경에서 iframe이 남을 수 있습니다. 타임아웃 기반 fallback 정리를 같이 두는 편이 안전합니다.

제안 diff
   const cleanup = () => {
     iframe.remove();
     printWindow.removeEventListener("afterprint", cleanup);
   };
   printWindow.addEventListener("afterprint", cleanup);
+  const fallbackCleanupTimer = window.setTimeout(cleanup, 60_000);

   const runPrint = () => {
     printWindow.focus();
     printWindow.print();
   };
@@
-  if (printDoc.fonts) {
+  if (printDoc.fonts) {
     void printDoc.fonts.ready.then(() => {
       setTimeout(runPrint, 100);
     });
   } else {
     setTimeout(runPrint, 300);
   }
+
+  const originalCleanup = cleanup;
+  const safeCleanup = () => {
+    window.clearTimeout(fallbackCleanupTimer);
+    originalCleanup();
+  };
+  printWindow.removeEventListener("afterprint", cleanup);
+  printWindow.addEventListener("afterprint", safeCleanup);

Also applies to: 62-68

🤖 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/overview/ai-summary/downloadAiSummaryPdf.ts` around lines
51 - 56, The cleanup currently runs only in the afterprint handler (cleanup,
printWindow, iframe), so add a timeout-based fallback that removes the iframe
and detaches the afterprint listener if afterprint never fires: create a timer
(e.g., via setTimeout) when adding printWindow.addEventListener("afterprint",
cleanup) that calls the same cleanup after a short delay, and ensure the cleanup
function clears that timer so it doesn't run twice; apply the same pattern to
the other print block (the second cleanup/addEventListener pair) so both code
paths have a timeout fallback and the timer is cleared inside cleanup.
🤖 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/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx`:
- Line 224: There is a typo in the Tailwind gradient class: replace any
occurrence of "bg-linear-to-r" with the correct "bg-gradient-to-r"; update the
className in the OverviewAiSummaryCard component (OverviewAiSummaryCard.tsx,
around the span with className containing bg-linear-to-r), and make the same
replacement in the LandingPage component (LandingPage.tsx) and the Button
component (Button.tsx) so all gradient usages use "bg-gradient-to-r".

In `@src/styles/aiReport.print.css`:
- Around line 2-9: The :root block defines hard-coded HEX color variables
(--color-text-title, --color-text-body, --color-text-muted, --color-surface-100,
--color-surface-200, --color-surface-400); remove these HEX declarations and
replace usages with the corresponding `@theme` tokens from tokens.css (do not
rename tokens or create new ones) so aiReport.print.css references the existing
theme color tokens instead of defining new hex values.

---

Duplicate comments:
In `@src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx`:
- Line 10: OverviewAiSummaryCard currently imports aiReportMockData directly;
remove this hard dependency by making the component accept the data via props or
by delegating fetch to a custom hook. Update the OverviewAiSummaryCard component
signature to accept a prop (e.g., aiReport: AiReportType) and replace all uses
of aiReportMockData with that prop, or create/use a hook named useAiReport that
returns the data and loading/error states and call it from the parent (not the
component) if you want composition; ensure PropTypes/TypeScript types
(AiReportType) and default/optional handling are added where referenced in
OverviewAiSummaryCard so the component no longer imports aiReportMockData.

---

Nitpick comments:
In `@src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx`:
- Line 5: OverviewSkeleton currently imports PLATFORM_ROAS_TABLE_COL from
PlatformRoasTable, creating tight coupling; either extract the grid constant
into a shared module (e.g., create and export PLATFORM_ROAS_TABLE_COL from a new
shared/constants or layout file and update both PlatformRoasTable and
OverviewSkeleton to import it) or duplicate the grid definition inside
OverviewSkeleton so it no longer imports PlatformRoasTable directly; update
imports in PlatformRoasTable and OverviewSkeleton to reference the new shared
export if you choose extraction.
- Around line 8-16: The OverviewKpiCardSkeleton currently has no accessible
loading semantics; update the top-level div in OverviewKpiCardSkeleton to
include role="status" and either aria-live="polite" or aria-busy="true" and add
a visually hidden text node (e.g., a span with screen-reader-only CSS content
like "데이터 로딩 중") so screen readers announce the loading state; apply the same
pattern to the other skeleton components in this file (the other top-level
skeleton wrappers referenced in the diff ranges) to ensure consistent accessible
loading indicators.

In `@src/pages/dashboard/overview/ai-summary/downloadAiSummaryPdf.ts`:
- Around line 51-56: The cleanup currently runs only in the afterprint handler
(cleanup, printWindow, iframe), so add a timeout-based fallback that removes the
iframe and detaches the afterprint listener if afterprint never fires: create a
timer (e.g., via setTimeout) when adding
printWindow.addEventListener("afterprint", cleanup) that calls the same cleanup
after a short delay, and ensure the cleanup function clears that timer so it
doesn't run twice; apply the same pattern to the other print block (the second
cleanup/addEventListener pair) so both code paths have a timeout fallback and
the timer is cleared inside cleanup.

In `@src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx`:
- Around line 260-276: The motion.div with key "ai-summary-panel" currently
includes the layout prop which forces Framer Motion to track children layout
changes and can hurt performance; remove the layout prop from that motion.div
(while keeping initial, animate, exit, transition, panelExitTransition,
panelTransition and the prefersReducedMotion handling) so the component
continues animating height and opacity without the extra layout tracking; verify
the visual behavior still matches expectations after removal.

In `@src/pages/dashboard/overview/sections/OverviewBudgetSection.tsx`:
- Around line 14-16: Imports in this file mix absolute and relative styles:
OverviewBudgetGaugeSkeleton is imported via "`@/components/`..." while
OverviewCampaignSnapshotCard uses a relative "./" path; pick the project's
convention and make them consistent (prefer using relative imports for
sibling/nearby components like OverviewCampaignSnapshotCard and any local
skeletons), update the import for OverviewBudgetGaugeSkeleton or
OverviewCampaignSnapshotCard so both use the same style and ensure the module
resolution still works for the chosen form.

In `@src/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsx`:
- Around line 24-28: PLATFORM_MAP is typed too loosely (Record<string,string>)
so PLATFORM_MAP[key] is string | undefined; tighten its type to either
Record<TProviderType, string> if every TProviderType is present, or
Partial<Record<TProviderType, string>> if some keys may be missing, and update
providerDisplayName to handle undefined explicitly (e.g., look up
PLATFORM_MAP[key] and return it or null) so the return type string | null is
honored; reference symbols: PLATFORM_MAP, providerDisplayName, TProviderType,
PROVIDERS.
🪄 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: 63832e36-4fe9-4637-8ee7-f1b1857d26d9

📥 Commits

Reviewing files that changed from the base of the PR and between 7a73cf3 and 14f7787.

📒 Files selected for processing (20)
  • src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx
  • src/pages/dashboard/overview/OverviewAiDrawer.tsx
  • src/pages/dashboard/overview/OverviewAiReportPanel.tsx
  • src/pages/dashboard/overview/OverviewDashboard.tsx
  • src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx
  • src/pages/dashboard/overview/ai-summary/aiReport.mock.ts
  • src/pages/dashboard/overview/ai-summary/aiReport.utils.ts
  • src/pages/dashboard/overview/ai-summary/downloadAiSummaryPdf.ts
  • src/pages/dashboard/overview/ai-summary/print/OverviewAiSummaryPrintReport.tsx
  • src/pages/dashboard/overview/sections/OverviewBudgetSection.tsx
  • src/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsx
  • src/pages/dashboard/overview/sections/OverviewKpiSection.tsx
  • src/pages/dashboard/overview/sections/OverviewPlatformSection.tsx
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/stories/Typography.stories.tsx
  • src/styles/aiReport.print.css
  • src/styles/print.css
  • src/styles/tokens.css
  • src/styles/utilities.css
  • src/types/dashboard/overview.ts
💤 Files with no reviewable changes (2)
  • src/pages/dashboard/overview/OverviewAiReportPanel.tsx
  • src/pages/dashboard/overview/OverviewAiDrawer.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/pages/dashboard/overview/ai-summary/print/OverviewAiSummaryPrintReport.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/dashboard/overview.ts

Comment thread src/pages/dashboard/overview/ai-summary/OverviewAiSummaryCard.tsx Outdated
Comment thread src/styles/aiReport.print.css Outdated
@Seojegyeong Seojegyeong merged commit 346089e into develop May 19, 2026
3 checks passed
@Seojegyeong Seojegyeong deleted the feature/#209 branch May 19, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 Html&css 마크업 & 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] AI 요약하기 디자인 구조 변경

3 participants