/** * 꾸미기 아이템 도메인 타입 — 순수 데이터 표현, 외부 의존 없음. * * **백엔드(edupay-backend)에 이 도메인의 API가 전혀 없다** — 조회조차 없다(사용자 확정 사항). * 그래서 이 타입의 실제 값은 전부 `lib/data/mock/decoration-item-store.ts`가 만들어 내고, * "백엔드 응답 → 도메인 매핑" 절차 자체가 존재하지 않는다 — admin-member.ts/student-member.ts와 * 달리 `null`이 "백엔드가 아직 주지 않는 항목"을 뜻하지 않는다(이 타입에서 `description`의 * `null`은 순수하게 "값 없음"이다). */ /** * 유형 — 개별아이템 / 셋트아이템. 시안(ADM_ITM_101, ADM_ITM_102_p) 두 곳 모두 "셋트아이템도 * 동일한 항목으로 처리하며 별도 화면이 없다"고 명시한다 — 목록의 유형 탭과 등록/수정 폼의 값이 * 모두 이 타입 하나를 공유한다. */ export type DecorationItemType = 'individual' | 'set'; export const DECORATION_ITEM_TYPE_OPTIONS: ReadonlyArray<{ value: DecorationItemType; label: string; }> = [ { value: 'individual', label: '개별아이템' }, { value: 'set', label: '셋트아이템' }, ]; export const DEFAULT_DECORATION_ITEM_TYPE: DecorationItemType = 'individual'; /** * 카테고리 — **백엔드 코드테이블이 없어 시안 예시값을 그대로 mock 상수로 둔다**(사용자 확정 * 사항. 시안 설명: "추후 확장성을 위해 필요, As is는 단일 카테고리로 운영"). 백엔드에 코드테이블 * API가 생기면 이 상수를 지우고 그 응답으로 대체한다. */ export const DECORATION_ITEM_CATEGORIES = ['계절', '축하', '시즌'] as const; export type DecorationItemCategory = (typeof DECORATION_ITEM_CATEGORIES)[number]; export type DecorationItem = { /** * 아이템ID — 등록 시 사용자가 직접 입력하는 유일 식별자(예: `ITEM-HAT-002`). 백엔드가 없어 * 별도 내부 id를 두지 않는다 — 목록 행의 key이자 수정/삭제 Server Action의 입력값이다. * 등록 후에는 변경할 수 없다(시안 ADM_ITM_103_p). */ itemId: string; itemType: DecorationItemType; name: string; category: DecorationItemCategory; /** 오픈 가능한 포인트. */ points: number; description: string | null; /** 사용여부 — 표에서는 O/X로 표기한다(`formatDecorationItemActiveLabel`). */ isActive: boolean; /** * 정렬순서 — 목록 기본 정렬 기준(오름차순, 시안 ADM_ITM_101). **유형(개별/셋트)마다 독립된 * 순번 공간이다** — 두 탭이 같은 화면을 재사용할 뿐 사실상 별개 목록이라, 한쪽의 정렬순서가 * 다른 쪽 순번에 영향을 주지 않는다(mock 생성기·Repository가 이 규칙을 지킨다). */ sortOrder: number; /** ISO 8601 문자열 — 수정일시. */ updatedAt: string; }; /** 사용여부 → 화면 표기(시안: O/X). */ export function formatDecorationItemActiveLabel(isActive: boolean): string { return isActive ? 'O' : 'X'; } /** 포인트 → 화면 표기(시안: "100 P" 형태). */ export function formatDecorationItemPoints(points: number): string { return `${points.toLocaleString('ko-KR')} P`; } /** * ISO 수정일시 → "YYYY-MM-DD HH:mm". mock이 생성하는 값이 항상 `Date#toISOString()` 형식 * (`YYYY-MM-DDTHH:mm:ss.sssZ`)이라 별도 날짜 라이브러리 없이 문자열 절단만으로 충분하다(신규 * 의존성 추가 금지 — CLAUDE.md §4.2). */ export function formatDecorationItemUpdatedAt(updatedAt: string): string { return updatedAt.slice(0, 16).replace('T', ' '); } /** * 썸네일 자리의 플레이스홀더 텍스트 — **의도적으로 이미지가 아니다.** 시안(ADM_ITM_102_p)은 * 썸네일 이미지 업로드 필드를 요구하지만, 사용자가 "썸네일을 따로 등록하지 않고 추후 백엔드가 * 자동생성해주는 형식"이라고 확정해 등록/수정 폼에서 업로드 필드를 제외했다(시안과 다르게 가는 * 지점). 목록의 썸네일 컬럼 자체는 유지하되(백엔드가 자동생성한 이미지를 표시할 자리), 지금은 * 생성해 줄 백엔드가 없어 아이템ID의 중간 세그먼트(예: `ITEM-HAT-002` → `HAT`)를 뽑아 대신 * 보여준다 — 실제 썸네일 API가 생기면 표 컴포넌트의 이 자리만 ``로 바꾸면 된다. */ export function getDecorationItemThumbnailLabel(itemId: string): string { const segments = itemId.split('-'); const middle = segments.length >= 2 ? segments[1] : itemId; return middle.slice(0, 3).toUpperCase(); }