# 선물 관리자 페이지 구현 프롬프트 이 문서는 관리자 페이지의 선물 관련 3개 화면을 프론트엔드 구현 에이전트에게 전달하기 위한 프롬프트 모음이다. 공통 전제: - 관리자 메뉴에는 parent 메뉴 `선물함 관리` 아래에 다음 3개 하위 메뉴가 있다. - `선물함 리스트` → `/gift/list` - `선물 카테고리` → `/gift/category` - `선물 사이즈` → `/gift/size` - 모든 API 응답은 기존 공통 envelope를 사용한다. 화면에서는 `response.data`를 실제 payload로 사용한다. - 인증/권한 처리는 기존 관리자 페이지의 API 클라이언트, 토큰 저장 방식, 에러 처리 방식을 따른다. - 새 디자인 시스템을 만들지 말고 기존 관리자 페이지의 테이블, 필터, 버튼, 모달, 폼, 토스트, 확인창 패턴을 재사용한다. - 화면 문구는 한국어로 작성한다. - API 실패 시 기존 관리자 페이지의 공통 에러 표시 방식을 따른다. --- ## 1. 선물함 리스트 페이지 프롬프트 ```text 관리자 선물함 리스트 페이지를 구현해줘. 목표: - 관리자 메뉴 `선물함 관리 > 선물함 리스트`에서 전체 선물 신청 목록을 조회한다. - 상태, 신청번호, 닉네임으로 필터링할 수 있다. - 목록 row에서 선물 상세를 확인하고, 현재 상태에서 가능한 운영 액션을 실행할 수 있다. - 기존 관리자 페이지의 테이블/필터/페이지네이션/모달/확인창 스타일을 그대로 따른다. 라우트: - `/gift/list` 필수 화면 구성: - 상단 제목: `선물함 리스트` - 필터 영역: - 상태 select: 전체, 접수 완료, 발송 확인, 사서함 도착, 검수 완료, 전달 완료, 전달 불가, 신청 취소 - 신청번호 검색 input: `applicationNo` 부분 검색 - 닉네임 검색 input: 발송인 또는 수취인 닉네임 부분 검색 - 검색 버튼, 초기화 버튼 - 목록 테이블 컬럼: - 신청번호 `applicationNo` - 발송인 닉네임 `senderNickname` - 수취인 닉네임 `recipientNickname` - 사이즈 `sizeName` / `sizeCode` - 카테고리 `categoryName` - 분류번호 `classificationNumber` - 택배사 `courierCompanyName` - 운송장번호 `trackingNumber` - 상태 `statusName` - 액션 버튼 - 페이지네이션: - 서버의 `page`, `size`, `totalCount`, `hasNext` 기준으로 기존 관리자 페이지 패턴에 맞춘다. 목록 조회 API: - Method: `GET` - URL: `/api/v2/admin/gifts` - Query: - `status?: GiftStatus` - `applicationNo?: string` - `nickname?: string` - `page?: number` 기본 `0` - `size?: number` 기본 `20` - 필터 조합: - 선택 필터는 AND 조건이다. - `nickname`은 발송인/수취인 닉네임 중 하나에 포함되면 매칭된다. 목록 조회 response `data`: ```ts type AdminGiftListResponse = { totalCount: number; items: AdminGiftListItemResponse[]; page: number; size: number; hasNext: boolean; }; type AdminGiftListItemResponse = { applicationNo: string; senderNickname: string; recipientNickname: string; sizeCode: string; sizeName: string; categoryName: string; classificationNumber: string; courierCompanyName: string | null; trackingNumber: string | null; status: GiftStatus; statusName: string; availableActions: AdminGiftAction[]; }; ``` 상태 enum: ```ts type GiftStatus = | "RECEIVED" | "TRACKING_REGISTERED" | "ARRIVED_AT_MAILBOX" | "INSPECTION_COMPLETED" | "DELIVERED" | "UNDELIVERABLE" | "CANCELED"; ``` 상태 표시명: - `RECEIVED`: `접수 완료` - `TRACKING_REGISTERED`: `발송 확인` - `ARRIVED_AT_MAILBOX`: `사서함 도착` - `INSPECTION_COMPLETED`: `검수 완료` - `DELIVERED`: `전달 완료` - `UNDELIVERABLE`: `전달 불가` - `CANCELED`: `신청 취소` 상세 조회 API: - Method: `GET` - URL: `/api/v2/admin/gifts/{applicationNo}` - 용도: - 목록 row 클릭 또는 `상세` 버튼 클릭 시 상세 모달/상세 패널을 연다. 상세 response `data`: ```ts type AdminGiftDetailResponse = { applicationNo: string; senderInfo: AdminGiftMemberInfoResponse; recipientInfo: AdminGiftMemberInfoResponse; productInfo: AdminGiftProductInfoResponse; inboundDeliveryInfo: AdminGiftInboundDeliveryInfoResponse; status: GiftStatus; statusName: string; availableActions: AdminGiftAction[]; }; type AdminGiftMemberInfoResponse = { nickname: string; name: string; phoneNumber: string; address: string; }; type AdminGiftProductInfoResponse = { sizeCode: string; sizeName: string; categoryName: string; classificationNumber: string; }; type AdminGiftInboundDeliveryInfoResponse = { courierCompanyName: string | null; trackingNumber: string | null; }; ``` 상세 표시 요구사항: - 발송인 정보: 닉네임, 이름, 전화번호, 주소 - 수취인 정보: 닉네임, 이름, 전화번호, 주소 - 상품 정보: 사이즈, 카테고리, 분류번호 - 입고 배송 정보: 택배사, 운송장번호 - 현재 상태와 가능한 액션 - 수취인이 배송지를 아직 입력하지 않은 경우 `recipientInfo.nickname`은 표시하고 `name`, `phoneNumber`, `address`는 빈 문자열로 온다. 화면에서는 `미입력`으로 표시해도 된다. 운영 액션 enum: ```ts type AdminGiftAction = | "ARRIVE_MAILBOX" | "COMPLETE_INSPECTION" | "COMPLETE_DELIVERY" | "MARK_UNDELIVERABLE"; ``` 상태별 `availableActions`: - `RECEIVED`: 없음 - `TRACKING_REGISTERED`: `ARRIVE_MAILBOX`, `MARK_UNDELIVERABLE` - `ARRIVED_AT_MAILBOX`: `COMPLETE_INSPECTION`, `MARK_UNDELIVERABLE` - `INSPECTION_COMPLETED`: `COMPLETE_DELIVERY`, `MARK_UNDELIVERABLE` - `DELIVERED`: 없음 - `UNDELIVERABLE`: 없음 - `CANCELED`: 없음 액션 버튼 문구: - `ARRIVE_MAILBOX`: `사서함 도착 처리` - `COMPLETE_INSPECTION`: `검수 완료 처리` - `COMPLETE_DELIVERY`: `전달 완료 처리` - `MARK_UNDELIVERABLE`: `전달 불가 처리` 액션 API: - 사서함 도착 처리 - Method: `POST` - URL: `/api/v2/admin/gifts/{applicationNo}/arrive-mailbox` - Body: 없음 - 검수 완료 처리 - Method: `POST` - URL: `/api/v2/admin/gifts/{applicationNo}/complete-inspection` - Body: 없음 - 전달 완료 처리 - Method: `POST` - URL: `/api/v2/admin/gifts/{applicationNo}/complete-delivery` - Body: 없음 - 전달 불가 처리 - Method: `POST` - URL: `/api/v2/admin/gifts/{applicationNo}/mark-undeliverable` - Body: ```ts type AdminGiftMarkUndeliverableRequest = { reason: string; }; ``` 액션 response `data`: ```ts type AdminGiftOperationStatusResponse = { applicationNo: string; status: GiftStatus; statusName: string; occurredAt: string; // UTC ISO string, e.g. 2026-09-30T12:00:00Z }; ``` 액션 UX: - 모든 상태 변경 액션은 확인창을 띄운다. - `MARK_UNDELIVERABLE`은 사유 입력 모달을 띄운다. - 전달 불가 사유는 공백만 입력할 수 없고 255자 이내로 제한한다. - 액션 성공 후: - 상세 모달이 열려 있으면 상세를 다시 조회한다. - 목록도 현재 필터/페이지 기준으로 다시 조회한다. - 성공 토스트를 표시한다. - 액션 실패 시 기존 관리자 페이지 에러 토스트/알림 패턴을 따른다. 빈 값 표시: - `courierCompanyName`, `trackingNumber`가 null이면 `-`로 표시한다. - 수취인 개인정보가 빈 문자열이면 `미입력`으로 표시한다. 테스트/검증: - API 클라이언트 함수 단위 테스트 또는 페이지 테스트에서 query parameter가 올바르게 전달되는지 확인한다. - `availableActions`에 따라 버튼 노출이 달라지는지 확인한다. - 전달 불가 사유 빈 값 validation을 확인한다. ``` --- ## 2. 선물 카테고리 CRUD 페이지 프롬프트 ```text 관리자 선물 카테고리 CRUD 페이지를 구현해줘. 목표: - 관리자 메뉴 `선물함 관리 > 선물 카테고리`에서 선물 카테고리를 조회, 등록, 수정, 비활성화한다. - 기존 관리자 페이지의 CRUD 테이블, 등록/수정 모달, 삭제 확인창, 토스트 패턴을 그대로 따른다. 라우트: - `/gift/category` 필수 화면 구성: - 상단 제목: `선물 카테고리` - 상단 우측 `카테고리 등록` 버튼 - 목록 테이블 컬럼: - 카테고리 ID `categoryId` - 분류번호 `classificationNumber` - 카테고리 코드 `categoryCode` - 카테고리명 `name` - 접수 코드 `receiptCode` - 대표 품목 `representativeItem` - 파손면책 동의 필요 여부 `requiresDamageWaiver` - 활성 여부 `isActive` - 관리 버튼: 수정, 비활성화 - 등록/수정 폼 필드: - `classificationNumber` - `categoryCode` - `name` - `receiptCode` - `representativeItem` - `requiresDamageWaiver` - `isActive` 목록 조회 API: - Method: `GET` - URL: `/api/v2/admin/gift-categories` - Request: 없음 - Response `data`: ```ts type AdminGiftCategoryResponse = { categoryId: number; classificationNumber: string; categoryCode: string; name: string; receiptCode: string; representativeItem: string; requiresDamageWaiver: boolean; isActive: boolean; }; ``` 등록 API: - Method: `POST` - URL: `/api/v2/admin/gift-categories` - Body: ```ts type AdminGiftCategoryRequest = { classificationNumber: string; categoryCode: string; name: string; receiptCode: string; representativeItem: string; requiresDamageWaiver: boolean; isActive: boolean; }; ``` - Response `data`: `AdminGiftCategoryResponse` 수정 API: - Method: `PUT` - URL: `/api/v2/admin/gift-categories/{categoryId}` - Body: `AdminGiftCategoryRequest` - Response `data`: `AdminGiftCategoryResponse` 비활성화 API: - Method: `DELETE` - URL: `/api/v2/admin/gift-categories/{categoryId}` - Body: 없음 - Response `data`: `AdminGiftCategoryResponse` - 서버는 실제 삭제가 아니라 `isActive=false` 논리 삭제로 처리한다. 폼 validation: - `classificationNumber`: 필수, 숫자 3자리. 예: `100` - `categoryCode`: 필수, 50자 이하. 예: `DOLL` - `name`: 필수, 50자 이하. 예: `인형` - `receiptCode`: 필수, 영문 대문자 1~9자. 예: `A` - `representativeItem`: 필수, 100자 이하. 예: `피규어` - `requiresDamageWaiver`: boolean - `isActive`: boolean UX 요구사항: - 등록 성공 후 목록을 다시 조회하고 모달을 닫는다. - 수정 성공 후 목록을 다시 조회하고 모달을 닫는다. - 비활성화 버튼은 확인창을 띄운 뒤 호출한다. - 이미 비활성화된 row는 비활성화 버튼을 disabled 처리한다. - boolean 값은 기존 관리자 페이지 표현에 맞춰 `예/아니오`, `활성/비활성` 또는 badge로 표시한다. - `categoryCode`는 수정 폼에 표시하되 서버 쪽 엔티티는 코드 변경을 실제로 반영하지 않을 수 있으므로, 가능하면 수정 시 읽기 전용으로 두고 나머지 운영 필드만 수정하도록 UX를 구성한다. 테스트/검증: - 목록 조회가 `GET /api/v2/admin/gift-categories`를 호출하는지 확인한다. - 등록/수정 body가 `AdminGiftCategoryRequest` 형태와 일치하는지 확인한다. - validation 실패 시 API를 호출하지 않고 폼 에러를 표시한다. - 비활성화 성공 후 해당 row의 `isActive=false`가 반영되는지 확인한다. ``` --- ## 3. 선물 사이즈 가격 CRUD 페이지 프롬프트 ```text 관리자 선물 사이즈 가격 CRUD 페이지를 구현해줘. 목표: - 관리자 메뉴 `선물함 관리 > 선물 사이즈`에서 선물 사이즈별 기본가/판매가/활성 여부를 조회하고 수정한다. - 현재 API는 사이즈 가격의 목록 조회와 수정만 제공한다. 신규 생성과 물리 삭제 기능은 만들지 않는다. - 기존 관리자 페이지의 테이블, 수정 모달, 저장 확인/토스트 패턴을 그대로 따른다. 라우트: - `/gift/size` 필수 화면 구성: - 상단 제목: `선물 사이즈` - 목록 테이블 컬럼: - 사이즈 코드 `sizeCode` - 사이즈명 `name` - 기본가 `basePriceCan` - 판매가 `salePriceCan` - 활성 여부 `isActive` - 관리 버튼: 수정 - 수정 폼 필드: - `sizeCode`: 읽기 전용 - `name`: 읽기 전용 - `basePriceCan`: number input - `salePriceCan`: number input - `isActive`: checkbox/switch 목록 조회 API: - Method: `GET` - URL: `/api/v2/admin/gift-size-prices` - Request: 없음 - Response `data`: ```ts type AdminGiftSizePriceResponse = { sizeCode: GiftSize; name: string; basePriceCan: number; salePriceCan: number; isActive: boolean; }; ``` 수정 API: - Method: `PUT` - URL: `/api/v2/admin/gift-size-prices/{sizeCode}` - Body: ```ts type AdminGiftSizePriceRequest = { basePriceCan: number; salePriceCan: number; isActive: boolean; }; ``` - Response `data`: `AdminGiftSizePriceResponse` 사이즈 enum: ```ts type GiftSize = "SMALL" | "MEDIUM" | "LARGE"; ``` 서버 validation: - `salePriceCan`은 0보다 커야 한다. - `salePriceCan`은 `basePriceCan`보다 클 수 없다. 프론트 validation: - `basePriceCan`: 필수, 1 이상 정수 - `salePriceCan`: 필수, 1 이상 정수 - `salePriceCan <= basePriceCan` - validation 실패 시 API 호출을 막고 필드 에러를 표시한다. UX 요구사항: - 가격은 숫자 입력이지만 테이블 표시에서는 기존 관리자 페이지의 숫자 포맷을 따른다. - 수정 버튼 클릭 시 현재 row 값을 폼 초기값으로 넣는다. - 저장 전 확인창은 기존 관리자 페이지 패턴이 있으면 따른다. - 저장 성공 후 목록을 다시 조회하고 모달을 닫는다. - 신규 생성/삭제 버튼은 만들지 않는다. 테스트/검증: - 목록 조회가 `GET /api/v2/admin/gift-size-prices`를 호출하는지 확인한다. - 수정 저장 시 `PUT /api/v2/admin/gift-size-prices/{sizeCode}`와 request body가 정확한지 확인한다. - `salePriceCan > basePriceCan`일 때 API를 호출하지 않고 validation 메시지를 표시하는지 확인한다. - 저장 성공 후 목록 refresh가 일어나는지 확인한다. ``` --- ## 구현 시 참고할 공통 타입 ```ts type ApiResponse = { success: boolean; data: T; message?: string; }; type GiftStatus = | "RECEIVED" | "TRACKING_REGISTERED" | "ARRIVED_AT_MAILBOX" | "INSPECTION_COMPLETED" | "DELIVERED" | "UNDELIVERABLE" | "CANCELED"; type AdminGiftAction = | "ARRIVE_MAILBOX" | "COMPLETE_INSPECTION" | "COMPLETE_DELIVERY" | "MARK_UNDELIVERABLE"; type GiftSize = "SMALL" | "MEDIUM" | "LARGE"; ``` ## 구현 순서 추천 1. API client 함수와 TypeScript 타입을 먼저 추가한다. 2. `/gift/list` 선물함 리스트와 상세/상태변경 모달을 구현한다. 3. `/gift/category` 카테고리 CRUD를 구현한다. 4. `/gift/size` 사이즈 가격 수정 화면을 구현한다. 5. 기존 관리자 메뉴의 `선물함 관리` 하위 route와 연결되는지 확인한다. 6. 각 페이지별 loading/empty/error/success 상태를 확인한다.