Skip to content

feat: 내가게관리 메뉴관리 API 연동#61

Merged
yooseolhee merged 5 commits into
developfrom
feat/owner-menu-api
Feb 11, 2026
Merged

feat: 내가게관리 메뉴관리 API 연동#61
yooseolhee merged 5 commits into
developfrom
feat/owner-menu-api

Conversation

@yooseolhee

@yooseolhee yooseolhee commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

💡 개요

내가게관리 메뉴관리 API 연동

🔢 관련 이슈 링크

💻 작업내용

  • 메뉴 조회
  • 메뉴 등록
  • 메뉴 삭제
  • 메뉴 이미지 선업로드
  • 메뉴 수정
  • 품절 여부 변경
  • 등록된 메뉴 이미지 삭제

📌 변경사항PR

  • FEAT: 새로운 기능 추가
  • FIX: 버그/오류 수정
  • CHORE: 코드/내부 파일/설정 수정
  • DOCS: 문서 수정(README 등)
  • REFACTOR: 코드 리팩토링 (기능 변경 없음)
  • TEST: 테스트 코드 추가/수정
  • STYLE: 스타일 변경(포맷, 세미콜론 등)

🤔 추가 논의하고 싶은 내용

  • 적용 안된 것처럼 보일 때 새로고침 한 번씩 해주세요!

✅ 체크리스트

  • 브랜치는 잘 맞게 올렸는지
  • 관련 이슈를 맞게 연결했는지
  • 로컬에서 정상 동작을 확있했는지
  • 충돌이 없다(또는 브랜치에서 충돌 해결 후 PR 업데이트 완료)

Summary by CodeRabbit

  • 새로운 기능

    • 메뉴의 생성·수정·삭제 및 이미지 업로드/삭제 지원
    • 메뉴에 이미지 미리보기와 이미지 관리(업로드/삭제) UI 추가
    • 메뉴별 품절 상태 토글(서버 동기화) 추가
    • 메뉴 추가/편집 시 서버와 로컬(브라우저) 동기화 및 저장
  • 수정/정리

    • 코드 주석/포맷 소소한 정리 및 타입에 이미지 키(imageKey) 필드 추가

@yooseolhee yooseolhee self-assigned this Feb 11, 2026
@yooseolhee yooseolhee added the API label Feb 11, 2026
@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

내 가게 관리 메뉴 기능을 서버 API와 연동합니다. 신규 API 모듈을 추가하고 메뉴 폼 및 관리 컴포넌트에 이미지 업로드/삭제, CRUD, 판매중단 토글과 로컬/서버 병합 로직을 구현했습니다.

Changes

Cohort / File(s) Summary
API — axios + owner menus
src/api/axios.ts, src/api/owner/menus.ts
src/api/owner/menus.ts 추가: 서버 메뉴 관련 타입들과 get/create/upload/delete/update 등 8개 이상의 API 함수 구현. src/api/axios.ts에 공백 추가(비기능적). 주의: OWNER_TOKEN 사용한 인증 헤더 포함.
Owner UI — 메뉴 폼 모달
src/components/owner/menuFormModal.tsx
이미지 업로드/미리보기/삭제 상태 및 비동기 제출 로직(생성 vs 편집) 추가. storeId prop 및 선택적 onImageDelete 콜백 도입. 에러/검증 분기 추가.
Owner UI — 메뉴 관리 페이지
src/components/owner/menuManagement.tsx
서버 getMenus 연동, 서버→로컬 매핑(mapServerToLocal), 로컬Storage 병합/영속화, 판매중단 토글을 서버로 반영, 삭제(create/update 흐름 포함) 처리. UI에서 이미지 렌더링 및 이미지 삭제 콜백 연동. 카테고리 편집/드래그 기능 제거.
Types
src/types/menus.ts
MenuItemimageKey?: string 필드 추가.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as MenuFormModal
    participant Management as MenuManagement
    participant API as Owner API
    participant Server

    rect rgba(100,150,200,0.5)
    Note over User,Server: 이미지 업로드 → 메뉴 생성 흐름
    User->>UI: 이미지 선택
    UI->>API: POST /stores/{id}/menus/images (FormData)
    API->>Server: 업로드 요청
    Server-->>API: { imageKey, imageUrl }
    API-->>UI: 응답 전달
    UI->>UI: imageUrl, imageKey 상태 설정
    User->>UI: 메뉴 정보 제출
    UI->>API: POST /stores/{id}/menus { menus }
    API->>Server: 메뉴 생성 요청 (Authorization)
    Server-->>API: 생성된 메뉴 데이터
    API-->>Management: 생성 응답 전달
    Management->>Management: 로컬 merge 및 localStorage 저장
    end

    rect rgba(200,100,100,0.5)
    Note over User,Server: 이미지 삭제(편집 모드)
    User->>UI: 이미지 삭제 클릭
    UI->>API: DELETE /stores/{id}/menus/{menuId}/image
    API->>Server: 이미지 삭제 요청 (Authorization)
    Server-->>API: 삭제 성공/에러
    API-->>UI: 결과 전달
    UI->>Management: onImageDelete 콜백 호출 (로컬 상태 갱신)
    Management->>Management: localStorage 업데이트
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

feat

Suggested reviewers

  • jjjsun
  • dew102938

Poem

🥢 작은 이미지 하나가 떠나와
서버로 건너가 반짝이며 돌아오네
업로드, 수정, 삭제 손끝의 춤사위
로컬과 서버가 맞춰가는 리듬 속에
가게의 메뉴가 또 하나 빛이 나네 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 제목은 이 변경사항의 핵심인 메뉴관리 API 연동을 명확하게 전달하며 FEAT 컨벤션을 따릅니다.
Description check ✅ Passed PR 설명에서 개요, 관련 이슈, 작업 내용, 변경사항 유형이 모두 포함되어 있으며 체크리스트도 완료되었습니다.
Linked Issues check ✅ Passed 메뉴 조회, 등록, 삭제, 이미지 업로드, 수정, 품절 여부 변경, 이미지 삭제 등 API 연동 요구사항이 모두 구현되었습니다 [#60].
Out of Scope Changes check ✅ Passed 모든 변경사항이 메뉴 관리 API 연동과 관련된 범위 내에 있으며, 추가적인 불필요한 변경사항은 없습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/owner-menu-api

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.

@dew102938 dew102938 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

스웨거를 확인해 보니 카테고리 등록/삭제/수정 관련 API가 현재는 없습니다
그래서 카테고리 관리 기능은 아예 제외하는 게 어떨까요?
대신 카테고리는 서버에서 허용하는 값인 main, side, beverage, alcohol
네 가지만 사용할 수 있도록 고정하는 방향으로 수정이 필요할 것 같습니다

Comment thread src/components/owner/menuManagement.tsx Outdated
Comment thread src/components/owner/menuFormModal.tsx Outdated
Comment thread src/components/owner/menuFormModal.tsx 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: 8

🤖 Fix all issues with AI agents
In `@src/api/owner/menus.ts`:
- Line 92: The variable token retrieved via localStorage.getItem("accessToken")
in the menus handler is unused; either remove the unused const token declaration
or, if authentication/authorization was intended, replace the unused retrieval
with actual usage (e.g., pass token into the relevant functions or include it in
request headers). Locate the line with const token =
localStorage.getItem("accessToken") and either delete that declaration or wire
the token into the intended logic (e.g., function calls that require auth) so
it’s not left unused.
- Line 1: Remove the broken OWNER_TOKEN import from src/api/owner/menus.ts and
delete any manual Authorization header code in each exported function so the
axios request interceptor in api/axios.ts (which uses useAuthStore and handles
token refresh/retry) can manage tokens; also remove the unused local variable
token in uploadMenuImage and change deleteMenuImage's catch to rethrow the
caught error instead of returning a fake success response so callers can detect
failures.

In `@src/components/owner/menuFormModal.tsx`:
- Around line 194-206: The bug is a mismatched property check: the code checks
editingMenu?.menuId but later reads editingMenu.id, causing the early-return to
always run; update the initial guard to check editingMenu?.id (or consistently
use menuId everywhere) so that when an editingMenu with an id exists the
function proceeds to call the server instead of only clearing local state; keep
the existing variables setImageFile, setImageUrl, setImageKey and the menuId
constant (derived from editingMenu.id) so the delete flow executes correctly.
- Line 197: The code sets imageKey to an empty string in one place
(setImageKey("")) and to null in another, which breaks the imageKey !== null
guard and can send payload.imageKey = "" to the server; change the first
occurrence to setImageKey(null) and ensure all places consistently use null for
the “no image” state (update any other assignments or checks around imageKey,
setImageKey, and payload.imageKey so they treat null as the sole empty-state).
- Around line 33-36: The image-related state (imageFile and uploading) isn't
reset when the modal reopens, causing stale values to persist; inside the
existing useEffect that runs on modal open or when editingMenu changes (the same
effect that sets imageUrl/imageKey), call setImageFile(null) and
setUploading(false) (and optionally reset imageUrl/imageKey via
setImageUrl(null)/setImageKey(null) when creating a new menu) so that imageFile,
uploading, imageUrl, and imageKey are properly initialized whenever the modal is
opened or editingMenu changes.

In `@src/components/owner/menuManagement.tsx`:
- Around line 195-198: The code in toggleSoldOutOnServer incorrectly tests
existence instead of the actual boolean value (newSoldOut is set using
(res.result as any).isSoldOut !== undefined), causing false to be treated as
true; change the assignment to derive a real boolean from res.result.isSoldOut
(e.g., const newSoldOut = !!(res.result && (res.result as any).isSoldOut) or
explicitly compare to true) and then use that newSoldOut when calling setMenus
and in the alert so true/false from the server is respected.
- Around line 164-183: The deleteMenu handler currently always converts id via
Number(id) which yields NaN for local-temp IDs like "MENU_{timestamp}" created
by mapServerToLocal; update deleteMenu to detect local IDs (e.g., id
startsWith('MENU_') or Number(id) is NaN) and in that branch avoid calling
deleteMenus: just call setMenus(prev => prev.filter(m => m.id !== id)) (or
compare as strings) and show a local-deletion alert, returning early; otherwise
proceed with the existing numeric conversion, deleteMenus(storeId, [menuIdNum])
flow and success/error handling.
- Line 325: MenuManagement currently uses a non-null assertion on storeId when
rendering MenuFormModal (storeId!) despite MenuManagementProps declaring
storeId?: string; remove the non-null assertion and add a guard so the modal is
only opened or given a storeId when it exists — e.g., check storeId in render or
in the opening handlers (handleAddClick / handleEditClick) and prevent opening
the modal or call API unless storeId is defined; alternatively, adjust
MenuFormModal to accept undefined safely, but the simplest fix is to gate modal
open/prop passing on a defined storeId to avoid invalid API URLs.
🧹 Nitpick comments (5)
src/api/owner/menus.ts (2)

104-127: deleteMenuImage가 에러를 삼키고 가짜 응답을 반환해요

catch 블록에서 isSuccess: false인 응답 객체를 만들어 반환하고 있는데, 호출부에서 이게 실제 서버 응답인지 로컬 에러 fallback인지 구분할 수 없어요. 또한 err: any 대신 타입을 좁히는 게 좋겠어요.

다른 API 함수들(deleteMenus, updateMenu 등)은 에러를 그대로 throw하는데 이 함수만 패턴이 달라서 혼란을 줄 수 있어요. 에러를 그대로 throw하고, 호출부(menuFormModal.tsx)에서 catch로 처리하는 게 일관성 있습니다.

♻️ 에러를 그대로 전파하는 방식
 export const deleteMenuImage = async (
   storeId: string,
   menuId: string
 ): Promise<ApiResponse<{ deletedImageKey: string }>> => {
-  try {
     const res = await api.delete<ApiResponse<{ deletedImageKey: string }>>(
         `/api/v1/stores/${storeId}/menus/${menuId}/image`,
         {
             headers: {
                 Authorization: `Bearer ${OWNER_TOKEN}`,
             }
         }
     );
     return res.data;
-  } catch (err: any) {
-    console.error('deleteMenuImage error', err);
-    return {
-      isSuccess: false,
-      code: '_MENU_IMAGE_DELETE_FAILED',
-      message: err?.response?.data?.message || '이미지 삭제 실패',
-      result: { deletedImageKey: '' },
-    };
-  }
 };

As per coding guidelines, src/api/**: 에러 처리/타임아웃/리트라이 전략 확인, 응답 타입 안전성 유지.


60-69: DeleteMenusResponseApiResponse<T>를 재사용하지 않아요

ApiResponse<T> 타입이 이미 @/types/api에 정의되어 있는데, DeleteMenusResponse가 동일한 구조를 다시 선언하고 있어요. ApiResponse<{ deletedMenuIds: number[] }>로 대체하면 중복을 줄일 수 있어요.

♻️ 타입 재사용
-export interface DeleteMenusResponse {
-  isSuccess: boolean;
-  code: string;
-  result: { deletedMenuIds: number[] };
-  message: string;
-}
+export type DeleteMenusResponse = ApiResponse<{ deletedMenuIds: number[] }>;
src/components/owner/menuFormModal.tsx (1)

9-13: any 타입 사용 — props 타입 안전성 개선 권장

onSubmit: (menuData: any), editingMenu?: any에서 any를 사용하고 있어요. LocalMenu 또는 전용 인터페이스로 타입을 지정하면 런타임 오류를 사전에 방지할 수 있어요. 지금 당장은 어렵다면, 나중에라도 개선해주세요.

As per coding guidelines, src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.

src/components/owner/menuManagement.tsx (2)

85-117: menus stale closure 주의 — useEffect 내 참조

Line 93에서 menus.filter(...)를 호출하는데, 이 menus는 effect 생성 시점의 값이에요. restaurantId가 변경될 때 이전 menus 상태를 읽을 수 있어요. eslint-disable react-hooks/exhaustive-deps로 경고를 끈 상태인데, 실제로 문제가 될 가능성은 낮지만 인지하고 계시면 좋겠어요.

로컬 임시 메뉴 병합이 중요하다면, setMenus(prev => ...) 콜백 패턴으로 변경하는 게 안전합니다.

♻️ 콜백 패턴 사용
-         const localTempMenus = menus.filter(m => m.id.startsWith('MENU_'));
-         const mergedMenus = [...serverMenus, ...localTempMenus];
-         setMenus(mergedMenus);
-         localStorage.setItem(STORAGE_KEY_MENU, JSON.stringify(mergedMenus));
+         setMenus(prev => {
+           const localTempMenus = prev.filter(m => m.id.startsWith('MENU_'));
+           const mergedMenus = [...serverMenus, ...localTempMenus];
+           localStorage.setItem(STORAGE_KEY_MENU, JSON.stringify(mergedMenus));
+           return mergedMenus;
+         });

43-50: any[]any 타입 사용 — LocalMenu 타입 활용 권장

LocalMenu 인터페이스를 이미 정의해두셨으니, menuseditingMenu 상태에도 적용하면 타입 안전성이 올라가요. 급하지 않으면 후속 작업으로도 괜찮을 것 같아요.

♻️ 타입 적용
-const [menus, setMenus] = useState<any[]>([]);
+const [menus, setMenus] = useState<LocalMenu[]>([]);
 // ...
-const [editingMenu, setEditingMenu] = useState<any>(null);
+const [editingMenu, setEditingMenu] = useState<LocalMenu | null>(null);

As per coding guidelines, src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.

Comment thread src/api/owner/menus.ts Outdated
Comment thread src/api/owner/menus.ts Outdated
Comment thread src/components/owner/menuFormModal.tsx
Comment thread src/components/owner/menuFormModal.tsx Outdated
Comment thread src/components/owner/menuFormModal.tsx Outdated
Comment thread src/components/owner/menuManagement.tsx
Comment thread src/components/owner/menuManagement.tsx Outdated
Comment thread src/components/owner/menuManagement.tsx

@jjjsun jjjsun left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

넵 확인했습니다! 바로 머지해주세요!

@yooseolhee yooseolhee merged commit 443e6a5 into develop Feb 11, 2026
@jjjsun jjjsun deleted the feat/owner-menu-api branch February 11, 2026 18:40
@jjjsun jjjsun added ✨ Feature 새로운 기능 추가 📪 API 서버 API 연결, 통신 labels Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📪 API 서버 API 연결, 통신 ✨ Feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat]내 가게 관리 메뉴 관리 API 연동

3 participants