diff --git a/docs/20260929_크리에이터_선물하기/admin-page-prompts.md b/docs/20260929_크리에이터_선물하기/admin-page-prompts.md new file mode 100644 index 00000000..22312c5d --- /dev/null +++ b/docs/20260929_크리에이터_선물하기/admin-page-prompts.md @@ -0,0 +1,470 @@ +# 선물 관리자 페이지 구현 프롬프트 + +이 문서는 관리자 페이지의 선물 관련 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 상태를 확인한다.