Skip to content

[Feature/#385] 캠페인 상세 플랫폼 예산 수정 및 ads 훅 패턴 정리 - #392

Merged
YermIm merged 16 commits into
developfrom
feature/#385
Aug 5, 2026
Merged

[Feature/#385] 캠페인 상세 플랫폼 예산 수정 및 ads 훅 패턴 정리#392
YermIm merged 16 commits into
developfrom
feature/#385

Conversation

@YermIm

@YermIm YermIm commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

close #385

✨ 변경사항

  • 🐞 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.)

✏️ 작업 내용

플랫폼 예산 수정

캠페인 상세에서 Meta / Google / Naver별 예산을 확인하고, 수정 버튼으로 바로 변경할 수 있게 했습니다.

  • 플랫폼마다 현재 쓰는 예산 타입 1개만 게이지로 표시 (Meta/Google: 일일 or 총예산 / Naver: 일일 예산)
  • 수정 버튼 → 모달에서 금액 입력 → 저장 시 해당 플랫폼 API 호출 → 상세 refetch로 게이지 갱신
  • 금액 입력 UX: 입력 중엔 숫자만, blur 시 콤마 포맷
스크린샷 2026-08-05 오전 6 59 26 스크린샷 2026-08-05 오전 6 59 51

ads 훅 패턴 통일

이전에 작업했었으나, 빠진 부분이 있어 같이 수정했습니다.

추가 훅

  • useUpdateCampaignStatus — 캠페인 일괄/선택 중단·재개
  • useUpdateAdStatus — 광고 소재 일괄/선택 중단·재개
  • useCreateTrackingUrl — 트래킹 URL 발급
    수정
  • useAdListuseCoreQuery 전환
  • queryKeys.tscampaign.ads 키 추가
  • AdsListPage, CampaignDetail, AdDetailContent, AdListTable

😅 미완성 작업

  • 캠페인 상세 platformBudgets 실데이터 연동 전 mock fallback 사용 중

📢 논의 사항 및 참고 사항

  • 현재 API 수정 전이라 mock data 사용 중이며 예산 수정이 정상 처리되지 않습니다.

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

Summary by CodeRabbit

새로운 기능

  • 새로운 기능

    • Meta·Google·Naver 캠페인의 플랫폼별 예산을 수정할 수 있습니다.
    • 일일 예산과 전체 예산을 구분해 편집하고 입력값을 검증합니다.
    • 캠페인과 광고 소재 상태를 일괄 변경할 수 있습니다.
    • 광고 트래킹 URL 발급 기능을 제공합니다.
  • 개선

    • 예산 수정 가능 여부와 제한 사유를 표시합니다.
    • 상태 변경 및 트래킹 URL 발급 후 데이터가 자동 갱신됩니다.
    • 예산 표시와 광고 목록 로딩 레이아웃을 개선했습니다.

@YermIm
YermIm requested review from Seojegyeong and jjjsun August 4, 2026 22:02
@YermIm YermIm self-assigned this Aug 4, 2026
@YermIm YermIm added 🎨 Html&css 마크업 & 스타일링 ✨ Feature 기능 개발 🔨 Refactor 코드 리팩토링 labels Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

플랫폼별 캠페인 예산 수정 API와 편집 모달을 추가했습니다. 예산 검증과 payload 생성을 플랫폼별로 분리했습니다. 광고 조회와 상태 변경은 React Query 기반 훅으로 전환했습니다. 캠페인 상세에 예산 편집과 일괄 상태 변경을 연결했습니다.

Changes

광고 관리 기능

Layer / File(s) Summary
예산 계약과 API
src/types/ads/budget.ts, src/types/ads/campaign.ts, src/api/ads/budget.ts, src/lib/queryKeys.ts
Meta·Google·Naver의 예산 요청·응답 타입과 수정 API를 추가했습니다. Meta·Google은 PATCH, Naver는 PUT을 사용합니다.
예산 검증과 mutation 변수
src/utils/ads/budgetEdit.ts, src/utils/ads/formatBudgetInput.ts, src/hooks/ads/useUpdatePlatformBudget.ts
플랫폼별 예산 검증, 기본값, 입력 포맷, payload와 mutation 변수를 생성합니다.
예산 편집 UI
src/components/ads/EditPlatformBudgetModal.tsx, src/components/ads/CampaignPlatformSection.tsx, src/components/ads/PlatformBudgetItem.tsx, src/utils/ads/projectBudget.ts, src/components/common/modal/Modal.tsx, src/components/ads/skeleton/AdsSkeleton.tsx
예산 유형에 따라 입력 필드와 스키마를 선택합니다. 수정 가능 여부에 따라 버튼을 제어합니다. 예산 게이지, 모달 종료 처리와 로딩 레이아웃을 변경했습니다.
캠페인 상세 통합
src/pages/ads/list/CampaignDetail.tsx
플랫폼 예산 편집 모달을 연결했습니다. 광고 상태 변경을 개별 요청에서 일괄 mutation으로 전환했습니다.
조회·상태 변경·트래킹 훅
src/hooks/ads/*, src/pages/ads/list/AdsListPage.tsx, src/components/ads/AdDetailContent.tsx, src/components/ads/AdListTable.tsx, src/utils/ads/settleBulkRequests.ts
광고 조회와 트래킹 URL 발급을 쿼리·mutation 훅으로 변경했습니다. 캠페인과 광고 상태 변경 요청을 정산하고 관련 쿼리를 무효화합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CampaignDetail
  participant EditPlatformBudgetModal
  participant useUpdatePlatformBudget
  participant BudgetAPI
  participant QueryCache
  CampaignDetail->>EditPlatformBudgetModal: 예산과 식별자 전달
  EditPlatformBudgetModal->>useUpdatePlatformBudget: 검증된 예산 변수 제출
  useUpdatePlatformBudget->>BudgetAPI: 플랫폼별 예산 수정 요청
  BudgetAPI-->>useUpdatePlatformBudget: 수정된 예산 데이터 반환
  useUpdatePlatformBudget->>QueryCache: 캠페인 상세 쿼리 무효화
  QueryCache-->>CampaignDetail: 최신 예산 데이터 조회
Loading

Possibly related PRs

Suggested labels: 📬 API

Suggested reviewers: jjjsun, seojegyeong

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 캠페인 상태 변경, 광고 상태 변경, 트래킹 URL과 광고 목록 훅 리팩터링은 예산 수정 이슈의 범위를 벗어납니다. 예산 수정과 무관한 상태 변경·트래킹 URL·광고 목록 훅 변경을 별도 PR로 분리하거나 관련 이슈를 추가해 범위를 명확히 하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 65.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 캠페인 상세의 플랫폼 예산 수정과 ads 훅 패턴 정리라는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Description check ✅ Passed 관련 이슈, 변경 유형, 작업 내용, 미완성 작업과 참고 사항을 모두 포함해 템플릿을 충실히 작성했습니다.
Linked Issues check ✅ Passed 플랫폼별 캠페인 예산 수정 UI, 모달, API 호출 및 예산 유형 처리가 직접 연결된 요구사항을 충족합니다 [#385].
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#385

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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📚 Storybook 배포 완료

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

@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: 9

🧹 Nitpick comments (11)
src/hooks/ads/useUpdatePlatformBudget.ts (1)

33-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

META와 GOOGLE 분기를 합칠 수 있습니다.

두 분기는 API 함수만 다릅니다. 검증과 payload 생성 로직은 같습니다. 맵으로 API 함수를 선택하면 중복이 줄고, 향후 검증 규칙을 한 곳에서 바꿀 수 있습니다.

♻️ 제안 리팩터
+        case "META":
+        case "GOOGLE": {
+          if (!vars.adCampaignId || !vars.activeBudgetType) {
+            throw new Error(
+              `${vars.providerType} 예산 수정 정보가 부족합니다.`,
+            );
+          }
+          const updateBudget =
+            vars.providerType === "META"
+              ? updateMetaCampaignBudget
+              : updateGoogleCampaignBudget;
+          await updateBudget(
+            vars.adCampaignId,
+            buildMetaGoogleBudgetPayload(vars.activeBudgetType, vars),
+          );
+          return;
+        }
🤖 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/hooks/ads/useUpdatePlatformBudget.ts` around lines 33 - 52, Refactor the
META and GOOGLE branches in the update flow to share the existing validation and
payload construction, selecting only the appropriate API function through a
platform-to-function map. Preserve the current missing-information errors and
invoke updateMetaCampaignBudget or updateGoogleCampaignBudget based on the
selected platform.
src/utils/ads/budgetEdit.ts (2)

107-115: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

non-null assertion 대신 값 검증을 넣어 주세요.

values.dailyBudget!values.lifetimeBudget!는 타입 검사만 통과시킵니다. 값이 실제로 undefined이면 payload 필드가 undefined가 되고, axios 직렬화에서 해당 키가 빠집니다. 결과적으로 빈 body가 PATCH로 전송됩니다. useUpdatePlatformBudget의 META/GOOGLE 분기에는 금액 검증이 없어서 이 경로가 열려 있습니다. NAVER 분기처럼 명시적으로 막는 편이 안전합니다.

♻️ 제안 리팩터
 export function buildMetaGoogleBudgetPayload(
   activeBudgetType: TPlatformBudgetType,
   values: { dailyBudget?: number; lifetimeBudget?: number },
 ): IMetaGoogleBudgetUpdateRequest {
   if (activeBudgetType === "DAILY") {
-    return { dailyBudget: values.dailyBudget! };
+    if (values.dailyBudget === undefined) {
+      throw new Error("일일 예산을 입력해 주세요.");
+    }
+    return { dailyBudget: values.dailyBudget };
   }
-  return { lifetimeBudget: values.lifetimeBudget! };
+  if (values.lifetimeBudget === undefined) {
+    throw new Error("전체 예산을 입력해 주세요.");
+  }
+  return { lifetimeBudget: values.lifetimeBudget };
 }
🤖 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/utils/ads/budgetEdit.ts` around lines 107 - 115, Replace the non-null
assertions in buildMetaGoogleBudgetPayload with explicit validation that the
selected dailyBudget or lifetimeBudget is defined before constructing the
payload. Reject missing amounts using the same established validation behavior
as the NAVER branch, while preserving the existing DAILY versus lifetimeBudget
payload selection.

44-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

naverBudgetFormSchemadailyBudgetFormSchema와 동일합니다.

두 스키마의 shape가 완전히 같습니다. 지금은 중복입니다. NAVER 전용 규칙이 확정되기 전까지는 별칭으로 두거나 dailyBudgetFormSchema를 재사용해도 됩니다. 다만 향후 NAVER 규칙이 달라질 계획이라면 현재 분리가 타당합니다. 판단해 주세요.

🤖 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/utils/ads/budgetEdit.ts` around lines 44 - 47, Update
naverBudgetFormSchema to reuse or alias dailyBudgetFormSchema because both
currently have the same shape; preserve the naverBudgetFormSchema export for
existing callers, unless a confirmed NAVER-specific validation rule requires
keeping a separate schema.
src/components/ads/EditPlatformBudgetModal.tsx (3)

190-195: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

formkey는 폼 상태를 초기화하지 않습니다.

useForm은 부모 컴포넌트에 있습니다. key를 바꾸면 form DOM 요소와 Controller 하위 트리만 재생성됩니다. React Hook Form의 내부 상태는 유지됩니다. 실제 초기화는 Line 137-140의 reset 호출이 담당합니다.

BudgetAmountInput의 로컬 상태를 초기화하는 목적이라면 지금 코드로 충분합니다. 그 외 목적이라면 key를 제거해도 동작이 같습니다. 의도를 확인해 주세요.

🤖 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/ads/EditPlatformBudgetModal.tsx` around lines 190 - 195,
Review the purpose of the form key in the EditPlatformBudgetModal form around
handleSubmit and onSubmit: do not rely on key changes to reset useForm state,
since reset is handled by the existing reset call around the form initialization
logic. Remove the key if it is only intended to reset form state; retain it only
if it is specifically required to remount BudgetAmountInput local state.

137-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

모달을 다시 열 때 이전 값이 남을 수 있는지 확인해 주세요.

resetisOpentrue이고 budget이 있을 때만 실행됩니다. 같은 budget으로 모달을 닫고 다시 열면 isOpenfalse → true로 바뀌므로 effect가 다시 실행됩니다. 이 경로는 정상입니다.

다만 저장 실패 후 모달을 닫지 않은 상태에서는 입력값이 그대로 유지됩니다. 이 동작이 의도된 것인지 확인해 주세요. 재시도를 위해 값을 유지하는 편이 보통 더 좋습니다.

🤖 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/ads/EditPlatformBudgetModal.tsx` around lines 137 - 140,
Verify the reset behavior in the useEffect around
resolveBudgetEditDefaultValues: reopening the modal with the same budget must
reset values when isOpen changes to true, while keeping user-entered values
intact after a save failure when the modal remains open. Preserve the current
dependency behavior unless it violates these requirements.

44-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

React 19 ref prop 방식으로 단순화하자.

프로젝트가 React 19.2+라서 forwardRef 없이 ref를 명시적인 props로 받을 수 있다. 새 컴포넌트에서는 IBudgetAmountInputPropsref?: React.Ref<HTMLInputElement>을 잡고, BudgetAmountInput 파라미터에서 { ref, ... }로 전달하는 방식이 더 간편하다.

🤖 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/ads/EditPlatformBudgetModal.tsx` around lines 44 - 48, Update
BudgetAmountInput to use the React 19 ref prop pattern instead of forwardRef:
add an optional React.Ref<HTMLInputElement> ref to IBudgetAmountInputProps,
receive ref alongside the other destructured props in BudgetAmountInput, and
remove the forwardRef wrapper while preserving the existing input ref behavior.
src/components/ads/CampaignPlatformSection.tsx (2)

46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

budgetEditDisabledReason 계산을 단순화할 수 있습니다.

3중 삼항 연산자를 쓰고 있습니다. editCheck.okfalse일 때만 사유가 필요합니다. 조건을 하나로 합치면 읽기 쉬워집니다.

♻️ 제안 리팩터
-  const budgetEditDisabledReason = !onEditBudget
-    ? undefined
-    : editCheck.ok
-      ? undefined
-      : BUDGET_EDIT_BLOCK_MESSAGES[editCheck.reason];
+  const budgetEditDisabledReason =
+    onEditBudget && !editCheck.ok
+      ? BUDGET_EDIT_BLOCK_MESSAGES[editCheck.reason]
+      : undefined;
🤖 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/ads/CampaignPlatformSection.tsx` around lines 46 - 52, Update
budgetEditDisabledReason in the CampaignPlatformSection logic to return a reason
only when onEditBudget is present and editCheck.ok is false; otherwise return
undefined. Replace the nested ternary with a single combined condition while
preserving the existing BUDGET_EDIT_BLOCK_MESSAGES[editCheck.reason] lookup.

84-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

게이지가 2개 이상이면 동일한 수정 버튼이 반복됩니다.

onEditBudget은 플랫폼 단위 콜백입니다. 그런데 버튼을 gauges.map 안에서 렌더합니다. 현재 mapPlatformProjectBudgetToGauges는 슬라이스 1개만 반환하므로 문제가 없습니다. 다만 향후 게이지가 여러 개로 늘어나면 같은 동작의 버튼이 중복 렌더됩니다.

첫 번째 게이지에만 headerAction을 전달하거나, 버튼을 섹션 header로 옮기는 방법을 고려해 주세요.

🤖 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/ads/CampaignPlatformSection.tsx` around lines 84 - 104, Update
the gauges.map rendering in CampaignPlatformSection so the platform-level
onEditBudget action is rendered only once, such as by passing headerAction to
the first gauge and omitting it from subsequent PlatformBudgetItem instances.
Preserve the existing disabled state, title, and button behavior.
src/pages/ads/list/CampaignDetail.tsx (1)

364-371: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

onEditBudget 콜백을 useCallback으로 감싸는 것을 검토해 주세요.

지금 인라인 화살표 함수가 매 렌더마다 새로 생성됩니다. CampaignPlatformSectionmemo가 아니므로 현재 렌더 비용 차이는 없습니다. 다만 platform을 인자로 받는 핸들러 하나를 useCallback으로 만들면, 향후 memo를 적용할 때 그대로 효과를 얻습니다.

♻️ 제안 리팩터
+  const handleEditBudget = useCallback(
+    (platform: TPlatform) => {
+      const budget = budgetByPlatform.get(platform);
+      if (budget) setBudgetEditTarget(budget);
+    },
+    [budgetByPlatform],
+  );
-                    onEditBudget={() => {
-                      const budget = budgetByPlatform.get(platform);
-                      if (budget) setBudgetEditTarget(budget);
-                    }}
+                    onEditBudget={() => handleEditBudget(platform)}
🤖 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/ads/list/CampaignDetail.tsx` around lines 364 - 371, Update the
CampaignDetail render flow to create the budget edit handler with useCallback
instead of defining the onEditBudget inline for each CampaignPlatformSection.
Make the callback accept the platform, retrieve the matching budget from
budgetByPlatform, and preserve the existing setBudgetEditTarget behavior, then
pass the stable handler to the section.
src/utils/ads/projectBudget.ts (1)

90-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mock 데이터의 activeBudgetType 결정 방식을 확인해 주세요.

index % 2DAILYLIFETIME을 번갈아 지정합니다. 플랫폼 배열의 순서에 따라 결과가 바뀝니다. 두 타입을 모두 확인하려는 의도라면 적절합니다. 다만 mock이므로 실데이터 연동 시 제거 대상임을 주석으로 남겨 두면 좋습니다.

🤖 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/utils/ads/projectBudget.ts` around lines 90 - 94, Review the mock-data
assignment of activeBudgetType in the surrounding budget-item construction and
confirm that alternating DAILY and LIFETIME by index is intentional for covering
both types. Add a concise comment marking this mock-only behavior as something
to remove when real data integration is implemented, without changing the
current assignment.
src/components/ads/AdDetailContent.tsx (1)

9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

내부 import에 @/ alias를 사용하세요.

Line 9부터 Line 12까지는 상대 경로를 사용합니다. @/components/common/... 경로로 변경하세요. 이 변경은 프로젝트 import 규칙을 위반합니다.

수정 예시
-import Badge from "../common/badge/Badge";
-import Button from "../common/button/Button";
-import Modal from "../common/modal/Modal";
-import ModalContent from "../common/modal/ModalContent";
+import Badge from "`@/components/common/badge/Badge`";
+import Button from "`@/components/common/button/Button`";
+import Modal from "`@/components/common/modal/Modal`";
+import ModalContent from "`@/components/common/modal/ModalContent`";

As per coding guidelines, use @/ alias for all imports.

🤖 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/ads/AdDetailContent.tsx` around lines 9 - 12, Update the
internal imports in AdDetailContent.tsx for Badge, Button, Modal, and
ModalContent to use the "`@/components/common/`..." alias paths instead of
relative paths, while preserving the existing imported symbols.

Source: Coding guidelines

🤖 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/ads/CampaignPlatformSection.tsx`:
- Around line 89-103: Move the disabled-state explanation from the Button’s
title to an accessible wrapper or visible helper text so
budgetEditDisabledReason remains available when isBudgetEditDisabled is true.
Update the budget edit control around onEditBudget without changing the existing
disabled behavior or styling.

In `@src/components/ads/EditPlatformBudgetModal.tsx`:
- Around line 174-180: Update the visible h2 in EditPlatformBudgetModal to
include aria-hidden so screen readers do not announce the title twice; keep the
Modal title prop as the sole accessible name.

In `@src/hooks/ads/useUpdateAdStatus.ts`:
- Around line 26-30: Update the batch status mutation in
src/hooks/ads/useUpdateAdStatus.ts lines 26-30 and
src/hooks/ads/useUpdateCampaignStatus.ts lines 30-34 so partial failures do not
leave successfully updated items with stale list caches. Replace the per-item
Promise.all approach with an atomic batch API, or track successful items and
individually refetch/update their caches after a failure while preserving
useCoreMutation invalidation behavior.

In `@src/pages/ads/list/CampaignDetail.tsx`:
- Around line 422-433: Update the batch mutation used by useUpdateAdStatus to
await parallel updateAdStatus calls with Promise.allSettled instead of
Promise.all, and return or propagate both success and failure counts. Adjust the
CampaignDetail onConfirm/useControlModal flow to consume these counts and
clearly notify the page of partial or complete success/failure while preserving
the existing ad selection and status payload logic.
- Around line 477-487: Update the CampaignDetail modal state flow so closing
EditPlatformBudgetModal does not immediately clear budgetEditTarget: introduce
or reuse separate open-state control, set it false in onClose, and clear the
target only after the modal’s exit animation completes. Preserve the existing
budget, orgId, and projectId values until unmount so Modal can restore focus and
run its closing animation.

In `@src/types/ads/budget.ts`:
- Around line 2-5: Update IMetaGoogleBudgetUpdateRequest to model an exclusive
union of the budget fields: require either dailyBudget or lifetimeBudget, while
disallowing the other field in each variant. Ensure empty requests and requests
containing both fields fail TypeScript validation.

In `@src/utils/ads/budgetEdit.ts`:
- Around line 107-115: Prevent undefined budget amounts in both update paths: in
src/utils/ads/budgetEdit.ts lines 107-115, update buildMetaGoogleBudgetPayload
to remove the non-null assertions and explicitly throw when the field matching
activeBudgetType is undefined; in src/hooks/ads/useUpdatePlatformBudget.ts lines
33-52, add the same activeBudgetType-specific amount validation to the META and
GOOGLE branches as the existing NAVER validation.
- Around line 93-99: Update the NAVER branch in the budget edit decision to
return a new NAVER-specific reason instead of NOT_EDITABLE. Add the
corresponding reason to the relevant reason type/enum and map it to a
user-facing message indicating that budget editing is currently being prepared
or unavailable pending backend specification confirmation. Keep the existing
commented NAVER context checks unchanged.

In `@src/utils/ads/projectBudget.ts`:
- Around line 51-66: 공통 활성 예산 타입 판정 함수를 추가하고, resolvePlatformBudgetDisplaySlice와
resolveBudgetEditFieldMeta가 모두 이를 사용하도록 변경하세요. activeBudgetType과 daily/lifetime
데이터 존재 여부를 동일하게 반영해 두 함수가 같은 예산 종류, 라벨, payload 필드를 선택하도록 하며, 기존 게이지와 모달의 반환 형식은
유지하세요.

---

Nitpick comments:
In `@src/components/ads/AdDetailContent.tsx`:
- Around line 9-12: Update the internal imports in AdDetailContent.tsx for
Badge, Button, Modal, and ModalContent to use the "`@/components/common/`..."
alias paths instead of relative paths, while preserving the existing imported
symbols.

In `@src/components/ads/CampaignPlatformSection.tsx`:
- Around line 46-52: Update budgetEditDisabledReason in the
CampaignPlatformSection logic to return a reason only when onEditBudget is
present and editCheck.ok is false; otherwise return undefined. Replace the
nested ternary with a single combined condition while preserving the existing
BUDGET_EDIT_BLOCK_MESSAGES[editCheck.reason] lookup.
- Around line 84-104: Update the gauges.map rendering in CampaignPlatformSection
so the platform-level onEditBudget action is rendered only once, such as by
passing headerAction to the first gauge and omitting it from subsequent
PlatformBudgetItem instances. Preserve the existing disabled state, title, and
button behavior.

In `@src/components/ads/EditPlatformBudgetModal.tsx`:
- Around line 190-195: Review the purpose of the form key in the
EditPlatformBudgetModal form around handleSubmit and onSubmit: do not rely on
key changes to reset useForm state, since reset is handled by the existing reset
call around the form initialization logic. Remove the key if it is only intended
to reset form state; retain it only if it is specifically required to remount
BudgetAmountInput local state.
- Around line 137-140: Verify the reset behavior in the useEffect around
resolveBudgetEditDefaultValues: reopening the modal with the same budget must
reset values when isOpen changes to true, while keeping user-entered values
intact after a save failure when the modal remains open. Preserve the current
dependency behavior unless it violates these requirements.
- Around line 44-48: Update BudgetAmountInput to use the React 19 ref prop
pattern instead of forwardRef: add an optional React.Ref<HTMLInputElement> ref
to IBudgetAmountInputProps, receive ref alongside the other destructured props
in BudgetAmountInput, and remove the forwardRef wrapper while preserving the
existing input ref behavior.

In `@src/hooks/ads/useUpdatePlatformBudget.ts`:
- Around line 33-52: Refactor the META and GOOGLE branches in the update flow to
share the existing validation and payload construction, selecting only the
appropriate API function through a platform-to-function map. Preserve the
current missing-information errors and invoke updateMetaCampaignBudget or
updateGoogleCampaignBudget based on the selected platform.

In `@src/pages/ads/list/CampaignDetail.tsx`:
- Around line 364-371: Update the CampaignDetail render flow to create the
budget edit handler with useCallback instead of defining the onEditBudget inline
for each CampaignPlatformSection. Make the callback accept the platform,
retrieve the matching budget from budgetByPlatform, and preserve the existing
setBudgetEditTarget behavior, then pass the stable handler to the section.

In `@src/utils/ads/budgetEdit.ts`:
- Around line 107-115: Replace the non-null assertions in
buildMetaGoogleBudgetPayload with explicit validation that the selected
dailyBudget or lifetimeBudget is defined before constructing the payload. Reject
missing amounts using the same established validation behavior as the NAVER
branch, while preserving the existing DAILY versus lifetimeBudget payload
selection.
- Around line 44-47: Update naverBudgetFormSchema to reuse or alias
dailyBudgetFormSchema because both currently have the same shape; preserve the
naverBudgetFormSchema export for existing callers, unless a confirmed
NAVER-specific validation rule requires keeping a separate schema.

In `@src/utils/ads/projectBudget.ts`:
- Around line 90-94: Review the mock-data assignment of activeBudgetType in the
surrounding budget-item construction and confirm that alternating DAILY and
LIFETIME by index is intentional for covering both types. Add a concise comment
marking this mock-only behavior as something to remove when real data
integration is implemented, without changing the current assignment.
🪄 Autofix

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 Plus

Run ID: e06ede7e-c6e6-462c-8db5-411160ca829b

📥 Commits

Reviewing files that changed from the base of the PR and between a75cf48 and c9addb0.

📒 Files selected for processing (20)
  • src/api/ads/budget.ts
  • src/components/ads/AdDetailContent.tsx
  • src/components/ads/AdListTable.tsx
  • src/components/ads/CampaignPlatformSection.tsx
  • src/components/ads/EditPlatformBudgetModal.tsx
  • src/components/ads/PlatformBudgetItem.tsx
  • src/components/ads/skeleton/AdsSkeleton.tsx
  • src/hooks/ads/useAdList.ts
  • src/hooks/ads/useCreateTrackingUrl.ts
  • src/hooks/ads/useUpdateAdStatus.ts
  • src/hooks/ads/useUpdateCampaignStatus.ts
  • src/hooks/ads/useUpdatePlatformBudget.ts
  • src/lib/queryKeys.ts
  • src/pages/ads/list/AdsListPage.tsx
  • src/pages/ads/list/CampaignDetail.tsx
  • src/types/ads/budget.ts
  • src/types/ads/campaign.ts
  • src/utils/ads/budgetEdit.ts
  • src/utils/ads/formatBudgetInput.ts
  • src/utils/ads/projectBudget.ts

Comment thread src/components/ads/CampaignPlatformSection.tsx Outdated
Comment thread src/components/ads/EditPlatformBudgetModal.tsx Outdated
Comment thread src/hooks/ads/useUpdateAdStatus.ts Outdated
Comment thread src/pages/ads/list/CampaignDetail.tsx
Comment thread src/pages/ads/list/CampaignDetail.tsx
Comment thread src/types/ads/budget.ts Outdated
Comment thread src/utils/ads/budgetEdit.ts Outdated
Comment thread src/utils/ads/budgetEdit.ts
Comment thread src/utils/ads/projectBudget.ts Outdated

@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: 1

🤖 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/utils/ads/projectBudget.ts`:
- Around line 62-76: Update the mock budget construction in the NAVER and
supportsDailyBudget(providerType) branches to stop exposing mock entries as
editable by removing or setting canEditBudget to a non-editable value. Keep the
existing budget data and mock identifiers unchanged, and do not add API fallback
states or edit UI behavior.
🪄 Autofix

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 Plus

Run ID: 0c2c34f2-55ce-4884-894e-1344d83254ac

📥 Commits

Reviewing files that changed from the base of the PR and between c9addb0 and 162ea47.

📒 Files selected for processing (13)
  • src/api/ads/budget.ts
  • src/components/ads/CampaignPlatformSection.tsx
  • src/components/ads/EditPlatformBudgetModal.tsx
  • src/components/common/modal/Modal.tsx
  • src/hooks/ads/useUpdateAdStatus.ts
  • src/hooks/ads/useUpdateCampaignStatus.ts
  • src/hooks/ads/useUpdatePlatformBudget.ts
  • src/pages/ads/list/CampaignDetail.tsx
  • src/types/ads/budget.ts
  • src/types/ads/campaign.ts
  • src/utils/ads/budgetEdit.ts
  • src/utils/ads/projectBudget.ts
  • src/utils/ads/settleBulkRequests.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/types/ads/campaign.ts
  • src/api/ads/budget.ts
  • src/components/ads/CampaignPlatformSection.tsx
  • src/hooks/ads/useUpdatePlatformBudget.ts
  • src/pages/ads/list/CampaignDetail.tsx
  • src/hooks/ads/useUpdateCampaignStatus.ts
  • src/components/ads/EditPlatformBudgetModal.tsx

Comment thread src/utils/ads/projectBudget.ts

@jjjsun jjjsun 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.

P4: 확인했습니다!

@YermIm
YermIm merged commit ae37de6 into develop Aug 5, 2026
3 checks passed
@YermIm
YermIm deleted the feature/#385 branch August 5, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] 캠페인 상세 플랫폼별 예산 수정 (캠페인 단위)

2 participants