Skip to content

feat(IN-271): 경쟁사 영상 분석 - 선택 액션바 및 AI 인사이트 구현#138

Merged
shunn2 merged 5 commits into
developfrom
feat/IN-271
Jun 3, 2026
Merged

feat(IN-271): 경쟁사 영상 분석 - 선택 액션바 및 AI 인사이트 구현#138
shunn2 merged 5 commits into
developfrom
feat/IN-271

Conversation

@shunn2

@shunn2 shunn2 commented May 30, 2026

Copy link
Copy Markdown
Collaborator

📌 작업 개요

  • 경쟁사 분석 페이지에 영상 카드 + 검색 결과 리스트업 구현
  • 선택 영상 액션 바 추가 (하단 sticky — 자기 자리에서 viewport 하단 24px 라인에 닿으면 고정, 다시 올리면 원위치 복귀)
  • AI 분석 인사이트 섹션 추가 — 콘텐츠 키워드 / 채널 특성 / 전략 인사이트 카드
  • 정렬 탭, 결과 없음/로딩 상태, 스크롤 투 탑 버튼 등 보조 UI
  • MSW mock handler 추가 (/brand-collaborations, /brand-collaborations/trends)

🗂 작업 유형

해당하는 항목에 x를 채워 주세요.

  • 기능 추가 (feat)
  • 버그 수정 (fix)
  • 리팩토링 (refactor)
  • 스타일 / UI 수정 (style)
  • 성능 개선 (perf)
  • 테스트 (test)
  • 기타 (chore, docs 등)

✏️ 작업 내용

✅ 셀프 체크리스트

머지 전 직접 확인해 주세요.

  • 로컬에서 정상 동작 확인
  • 불필요한 콘솔 로그, 주석, 디버그 코드 제거
  • 타입 에러 없음

💬 리뷰어에게

Summary by CodeRabbit

  • 새로운 기능

    • 브랜드 협업 트렌드 분석 기능 추가 — 선택한 영상으로 AI 기반 트렌드 인사이트 생성
    • 경쟁사 인사이트 섹션 추가 — 콘텐츠 공통 키워드, 채널 특성, 전략 인사이트 카드 제공
    • 분석 로딩/오류 처리 및 재시도 UI 제공
  • 개선

    • 경쟁사 필터 패널의 상세 검색 토글 외부 제어 지원
    • 경쟁사 페이지의 분석 흐름·선택·리셋 동작 개선 — 분석 상태 관리 및 시각화 안정화

@vercel

vercel Bot commented May 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inflace Ready Ready Preview, Comment Jun 3, 2026 1:50pm

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

🎯 Walkthrough

유튜브 영상 ID 목록으로 POST /brand-collaborations/trends를 호출해 콘텐츠 키워드, 채널 특성, 전략 인사이트를 받아오는 타입·API·훅·MSW 핸들러와 이를 렌더링하는 인사이트 카드·섹션, 페이지·필터 연동을 추가합니다.

📋 Changes

트렌드 분석 기능 개발

Layer / File(s) Summary
데이터 계약 및 API 함수
src/features/competitor/model/types.ts, src/features/competitor/api/competitorApi.ts, src/features/competitor/index.ts
요청/응답 DTO 타입(BrandCollaborationsTrendsRequest, BrandCollaborationsTrendsResponseDto 등)과 POST API 함수 fetchBrandCollaborationsTrends를 정의하고 기능 모듈 인덱스에서 타입과 함수를 재-export합니다.
React Query 훅
src/features/competitor/model/useBrandCollaborationsTrends.ts
정렬된 영상 ID를 캐시 키로 사용하고, 빈 배열이나 10개 초과 시 쿼리를 비활성화하며, 동일 영상셋 유지 시 stale 처리 없이 유지하는 useBrandCollaborationsTrends 훅을 구현합니다.
테스트 인프라
src/shared/api/msw/handlers/brandCollaborationsHandlers.ts, src/shared/api/msw/handlers/videosHandlers.ts
/brand-collaborations/trends 엔드포인트의 MSW 핸들러와 고정 키워드 데이터를 추가하고, videos 핸들러의 성공 필드 이름을 success로 조정합니다.
기본 카드 컴포넌트
src/widgets/competitor/competitorInsight/ui/ImpactCard.tsx
아이콘·타이틀·내용을 담는 재사용 가능한 카드 레이아웃을 정의하여 인사이트 카드들의 공통 스타일과 구조를 제공합니다.
인사이트 카드 모음
src/widgets/competitor/competitorInsight/ui/ContentKeywordsCard.tsx, ChannelCharacteristicsCard.tsx, StrategyInsightCard.tsx
콘텐츠 키워드(상위 강조), 채널 메트릭 및 카테고리 분포, PPL 의도 및 경쟁 포인트를 각각 ImpactCard로 감싸 렌더링합니다.
트렌드 분석 섹션
src/widgets/competitor/competitorInsight/ui/CompetitorInsightSection.tsx, src/widgets/competitor/competitorInsight/index.ts, src/widgets/competitor/index.ts
videoIds를 기반으로 훅을 호출하고, 로딩/에러/성공 상태를 처리하며, 성공 시 3개 카드를 세로 배치로 렌더링하고 완료 콜백을 트리거합니다.
필터 패널 상태 제어 확장
src/widgets/competitor/competitorFilter/ui/CompetitorFilterPanel.tsx
상세 검색 토글 상태를 isDetailOpen/onDetailOpenChange prop으로 제어하여 부모 컴포넌트와 상태를 동기화합니다.
경쟁 분석 페이지 통합
src/pages/competitor/ui/CompetitorPage.tsx
analyzedVideoIdsisDetailOpen 상태를 추가하고, 핸들러들을 재정의하여 분석 결과 상태를 관리하며, CompetitorInsightSection 렌더링과 필터 패널 제어를 연결합니다.

🎯 Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

👥 Suggested reviewers

  • juuhye
  • kimYunHyeong

🐰 Poem

토끼가 영상들을 모아 뛰어와,
키워드 꽃을 꿰고 통계를 헤아려,
카드에 담긴 인사이트를 살짝 보여주네,
클릭 한 번에 트렌드가 반짝,
분석의 당근을 향해 토끼가 폴짝! 🥕✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명이 작업 개요, 작업 유형, 셀프 체크리스트 항목을 포함하고 있으나, 필수 섹션인 '✏️ 작업 내용' 항목이 비워있습니다. '✏️ 작업 내용' 섹션을 작성하여 구체적인 변경 사항과 구현 세부 사항을 설명해 주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항인 경쟁사 영상 분석 기능(선택 액션바 및 AI 인사이트 구현)을 명확하게 요약하고 있습니다.
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.

✏️ 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 feat/IN-271

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.

@shunn2 shunn2 requested review from juuhye and kimYunHyeong May 30, 2026 14:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/pages/competitor/ui/CompetitorPage.tsx (1)

60-66: ⚡ Quick win

초기화 시 상세 검색 열림 상태도 함께 리셋하는 편이 일관적입니다.

Line 61의 handleReset은 필터/선택/분석 상태를 모두 초기화하는데, isDetailOpen은 유지됩니다. 초기화 의미를 맞추려면 setIsDetailOpen(false)도 포함해 주세요.

수정 예시
   function handleReset() {
     setDraftFilter(DEFAULT_COMPETITOR_FILTER)
     setAppliedFilter(null)
     setSelectedVideoIds(new Set())
     setAnalyzedVideoIds([])
+    setIsDetailOpen(false)
   }
🤖 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/competitor/ui/CompetitorPage.tsx` around lines 60 - 66, handleReset
currently resets draft/applied filters and selected/analyzed video state but
leaves the detail-panel open state untouched; update the handleReset function to
also call setIsDetailOpen(false) so the isDetailOpen state is reset when
clearing filters/selection/analysis (look for the handleReset definition and add
setIsDetailOpen(false) alongside setDraftFilter, setAppliedFilter,
setSelectedVideoIds, and setAnalyzedVideoIds).
src/widgets/competitor/competitorFilter/ui/CompetitorFilterPanel.tsx (1)

105-107: ⚡ Quick win

상세 검색 토글에 ARIA 상태를 연결해 주세요.

Line 114의 토글 버튼은 시각적으로는 동작하지만, 스크린리더에는 펼침 상태가 전달되지 않습니다. aria-expandedaria-controls를 추가하고, Line 105의 상세 영역에 id를 연결해 주세요.

수정 예시
-        {isDetailOpen && (
-          <CompetitorFilterDetailFields filter={filter} onChange={onChange} />
-        )}
+        {isDetailOpen && (
+          <div id='competitor-detail-filters'>
+            <CompetitorFilterDetailFields filter={filter} onChange={onChange} />
+          </div>
+        )}

         <button
           type='button'
           onClick={() => setIsDetailOpen(!isDetailOpen)}
+          aria-expanded={isDetailOpen}
+          aria-controls='competitor-detail-filters'
           className='flex cursor-pointer items-center gap-4 rounded-full px-16 py-6 text-noto-label-md-normal text-text-and-icon-secondary transition-colors hover:bg-background-gray-default'>

Also applies to: 114-117

🤖 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/widgets/competitor/competitorFilter/ui/CompetitorFilterPanel.tsx` around
lines 105 - 107, The detail toggle button currently doesn't expose its expanded
state to assistive tech; update the toggle (the element that flips isDetailOpen)
to include aria-expanded={isDetailOpen} and aria-controls pointing to the detail
region, and give the detail container rendered by CompetitorFilterDetailFields a
matching id (e.g. "competitor-filter-detail" or a generated id) so screen
readers receive the expanded/collapsed state; ensure the id passed to the detail
component matches the aria-controls string and keep using the existing props
(filter, onChange) when rendering CompetitorFilterDetailFields.
🤖 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/shared/api/msw/handlers/brandCollaborationsHandlers.ts`:
- Around line 69-70: The POST handler returning HttpResponse.json currently uses
the wrong response field name isSuccess; update that response object to use
success: true so it matches the ApiResponse<T> contract and the GET handler and
aligns with fetchBrandCollaborationsTrends expectations—locate the POST
/brand-collaborations/trends handler that calls HttpResponse.json and replace
isSuccess with success (and ensure any tests or consumers expect success).

In `@src/widgets/competitor/competitorInsight/ui/CompetitorInsightSection.tsx`:
- Around line 41-57: The StatusCard always shows a spinner which causes the
error UI to display a loading indicator; update StatusCard to accept a prop
(e.g., showSpinner: boolean) that controls spinner rendering, then in
CompetitorInsightSection's error branch (the block that returns <StatusCard>
when isError is true) pass showSpinner={false} so the error message and retry
Button render without the spinner; also update the other StatusCard usages
around the loading/empty branches (the similar block at lines ~70-83) to pass
showSpinner={true} only when loading is intended. Ensure the prop name
(showSpinner) is used consistently in StatusCard's implementation and all places
where StatusCard is rendered.
- Around line 33-35: 현재 CompetitorInsightSection의 useEffect([data,
onAnalysisComplete])가 data가 이미 있는 상태에서 부모(예: CompetitorPage)가 인라인
콜백(onAnalysisComplete={() => setIsDetailOpen(false)})을 매 렌더 재생성하면 의도한 “한 번만 호출”
동작이 깨질 수 있습니다; 수정 방법은 두 가지 중 하나를 선택하세요: 1) CompetitorPage에서 setIsDetailOpen 관련
콜백을 useCallback으로 안정화하여 onAnalysisComplete가 재생성되지 않게 하거나, 2)
CompetitorInsightSection 내부에서 useEffect 트리거를 onAnalysisComplete 변화가 아닌 data의 “첫
수신”으로 한정(예: useRef로 prevData 존재 여부 체크 또는 이미Handled 플래그)해 onAnalysisComplete를 한
번만 호출하도록 변경하세요; 관련 식별자: CompetitorInsightSection, useEffect, onAnalysisComplete,
CompetitorPage, setIsDetailOpen, useCallback, prev/data-handled ref.

In `@src/widgets/competitor/competitorInsight/ui/ImpactCard.tsx`:
- Line 46: The div in ImpactCard currently uses 'px-44' which applies 44px
padding on both sides and shrinks flex children; change the className on the
container div (the element rendering {children} in ImpactCard) from 'px-44' to
'pl-44' to apply left-only padding, and update the nearby comment that mentions
"44px left indent" (around the earlier comment) to reflect that we're using left
padding only.

---

Nitpick comments:
In `@src/pages/competitor/ui/CompetitorPage.tsx`:
- Around line 60-66: handleReset currently resets draft/applied filters and
selected/analyzed video state but leaves the detail-panel open state untouched;
update the handleReset function to also call setIsDetailOpen(false) so the
isDetailOpen state is reset when clearing filters/selection/analysis (look for
the handleReset definition and add setIsDetailOpen(false) alongside
setDraftFilter, setAppliedFilter, setSelectedVideoIds, and setAnalyzedVideoIds).

In `@src/widgets/competitor/competitorFilter/ui/CompetitorFilterPanel.tsx`:
- Around line 105-107: The detail toggle button currently doesn't expose its
expanded state to assistive tech; update the toggle (the element that flips
isDetailOpen) to include aria-expanded={isDetailOpen} and aria-controls pointing
to the detail region, and give the detail container rendered by
CompetitorFilterDetailFields a matching id (e.g. "competitor-filter-detail" or a
generated id) so screen readers receive the expanded/collapsed state; ensure the
id passed to the detail component matches the aria-controls string and keep
using the existing props (filter, onChange) when rendering
CompetitorFilterDetailFields.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08f0973a-38e5-4ace-9588-71b5504a570f

📥 Commits

Reviewing files that changed from the base of the PR and between 05e3033 and 497b1da.

📒 Files selected for processing (14)
  • src/features/competitor/api/competitorApi.ts
  • src/features/competitor/index.ts
  • src/features/competitor/model/types.ts
  • src/features/competitor/model/useBrandCollaborationsTrends.ts
  • src/pages/competitor/ui/CompetitorPage.tsx
  • src/shared/api/msw/handlers/brandCollaborationsHandlers.ts
  • src/widgets/competitor/competitorFilter/ui/CompetitorFilterPanel.tsx
  • src/widgets/competitor/competitorInsight/index.ts
  • src/widgets/competitor/competitorInsight/ui/ChannelCharacteristicsCard.tsx
  • src/widgets/competitor/competitorInsight/ui/CompetitorInsightSection.tsx
  • src/widgets/competitor/competitorInsight/ui/ContentKeywordsCard.tsx
  • src/widgets/competitor/competitorInsight/ui/ImpactCard.tsx
  • src/widgets/competitor/competitorInsight/ui/StrategyInsightCard.tsx
  • src/widgets/competitor/index.ts

Comment thread src/shared/api/msw/handlers/brandCollaborationsHandlers.ts Outdated
Comment thread src/widgets/competitor/competitorInsight/ui/ImpactCard.tsx Outdated

@kimYunHyeong kimYunHyeong left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

고생하셨습니다~ 코드 확인했습니다!

@shunn2 shunn2 merged commit 6247ae3 into develop Jun 3, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants