임동욱 임동욱 08-19
feat: 카테고리를 백엔드 공통코드로 — 프론트 임시 코드 제거
`GET /api/v1/mngr/code/list/ITEM_CATE_CD`(사용자 안내)로 카테고리를 가져온다. 종전에는
코드테이블이 정비되기 전이라 프론트에 임시 코드(CATE01 계절 / CATE02 축하 / CATE03 시즌)를
두고 있었다 — 그 상수를 지웠다.

- lib/domain/common-code.ts — 코드 그룹 도메인. 코드 그룹 ID를 한곳에 모아 화면이 문자열을
  직접 적지 않게 한다.
- lib/data/repositories/common-code-repository.ts — 조회. `cache()`로 감싸 한 요청 안에서
  화면(선택지 그리기)과 Server Action(입력값 검증)이 같은 목록을 한 번만 받아 쓰게 한다.
  그리는 데 쓴 목록과 검증에 쓴 목록이 반드시 같아진다는 점이 중요하다.
- 정렬은 백엔드 순서를 그대로 쓴다 — SQL이 아이템 목록과 같은 이중 역순 패턴이라 화면에서
  다시 정렬하면 다른 화면과 어긋난다.
- 코드 한 줄이 깨져도(코드값 없음) 그 줄만 건너뛴다. 목록 조회의 fail-fast와 다른 판단인데,
  선택지 하나 때문에 화면 전체를 못 쓰게 만들 이유가 없기 때문이다.

검증도 상수 대신 조회 결과를 쓴다. `validateDecorationItemCreate/Update`가 허용 코드 목록을
**인자로** 받고(domain 계층은 통신을 하지 않는다), Server Action이 저장 직전에 다시 조회해
넘긴다 — 화면이 보낸 코드를 그대로 믿으면 코드테이블에 없는 값이 저장된다.

카테고리 표기도 단순해졌다. 목록 SQL이 코드테이블을 조인해 `itemCateNm`을 내려 주므로
프론트 목록에서 이름을 찾던 두 번째 경로가 사라지고, 코드가 지워졌을 때만 코드값으로 떨어진다.

Co-Authored-By: Claude Opus 5 
@c0243a16d2705abc74125728b0652b5c374547a0
app/(protected)/(basic)/decoration-items/_actions.ts
--- app/(protected)/(basic)/decoration-items/_actions.ts
+++ app/(protected)/(basic)/decoration-items/_actions.ts
@@ -2,12 +2,14 @@
 
 import { revalidatePath } from 'next/cache';
 import { verifySession } from '@/lib/auth/dal';
+import { fetchCommonCodes } from '@/lib/data/repositories/common-code-repository';
 import {
   createDecorationItem,
   deleteDecorationItem,
   updateDecorationItem,
   uploadDecorationItemImage,
 } from '@/lib/data/repositories/decoration-item-repository';
+import { CODE_GROUP } from '@/lib/domain/common-code';
 import type { DecorationItemType } from '@/lib/domain/decoration-item';
 import {
   validateDecorationItemCreate,
@@ -24,7 +26,8 @@
  * POST될 수 있어 이 확인이 유일한 최종 방어선이다(설계서 §3 SRP 체크).
  *
  * 검증은 화면이 아니라 여기서 확정한다(`lib/domain/decoration-item-form.ts`의 규칙을 호출) —
- * 화면의 required 속성·기본값 채움은 편의일 뿐 신뢰 경계가 아니다.
+ * 화면의 required 속성·기본값 채움은 편의일 뿐 신뢰 경계가 아니다. **허용 카테고리도 여기서
+ * 다시 조회해 넘긴다** — 화면이 보낸 코드를 그대로 믿으면 코드테이블에 없는 값이 저장된다.
  *
  * **이미지 업로드도 이 계층을 지난다** — 브라우저는 백엔드를 직접 호출하지 않으므로(토큰이
  * httpOnly 세션 안에만 있다) 폼이 실어 보낸 File을 여기서 받아 Repository를 통해 백엔드에
@@ -119,7 +122,8 @@
     readEditableValues(
       formData,
       newFile ? PENDING_UPLOAD_PLACEHOLDER : currentImageFileId
-    )
+    ),
+    await fetchCommonCodes(CODE_GROUP.decorationItemCategory)
   );
   if (!validation.ok) {
     return { status: 'error', errors: validation.errors };
@@ -162,7 +166,8 @@
     readEditableValues(
       formData,
       newFile ? PENDING_UPLOAD_PLACEHOLDER : currentImageFileId
-    )
+    ),
+    await fetchCommonCodes(CODE_GROUP.decorationItemCategory)
   );
   if (!validation.ok) {
     return { status: 'error', errors: validation.errors };
app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
@@ -6,6 +6,7 @@
 import { FoxInput } from '@fox/core/components/fox-input';
 import { FoxModal } from '@fox/core/components/fox-modal';
 import { useFeedback } from '@/app/_hooks/use-feedback';
+import type { CommonCode } from '@/lib/domain/common-code';
 import type { DecorationItemType } from '@/lib/domain/decoration-item';
 import { INITIAL_DECORATION_ITEM_FORM_STATE } from '@/lib/domain/decoration-item-form';
 import { createDecorationItemAction } from '../_actions';
@@ -15,6 +16,8 @@
   /** 등록 버튼을 누른 시점의 활성 탭(유형) — 폼의 유형 기본값으로 쓴다. */
   defaultItemType: DecorationItemType;
   typeTotalCount: number;
+  /** 공통코드 `ITEM_CATE_CD` — 카테고리 선택지. */
+  categories: CommonCode[];
   onClose: () => void;
 }
 
@@ -32,6 +35,7 @@
 export function DecorationItemCreateModal({
   defaultItemType,
   typeTotalCount,
+  categories,
   onClose,
 }: DecorationItemCreateModalProps) {
   const { showToast } = useFeedback();
@@ -73,6 +77,7 @@
         <DecorationItemFormFields
           defaultItemType={defaultItemType}
           typeTotalCount={typeTotalCount}
+          categories={categories}
           errors={errors}
           itemIdField={
             <FoxInput
app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
@@ -6,6 +6,7 @@
 import { FoxInput } from '@fox/core/components/fox-input';
 import { FoxModal } from '@fox/core/components/fox-modal';
 import { useFeedback } from '@/app/_hooks/use-feedback';
+import type { CommonCode } from '@/lib/domain/common-code';
 import type { DecorationItem } from '@/lib/domain/decoration-item';
 import { INITIAL_DECORATION_ITEM_FORM_STATE } from '@/lib/domain/decoration-item-form';
 import { updateDecorationItemAction } from '../_actions';
@@ -14,6 +15,8 @@
 interface DecorationItemEditModalProps {
   item: DecorationItem;
   typeTotalCount: number;
+  /** 공통코드 `ITEM_CATE_CD` — 카테고리 선택지. */
+  categories: CommonCode[];
   onClose: () => void;
 }
 
@@ -28,6 +31,7 @@
 export function DecorationItemEditModal({
   item,
   typeTotalCount,
+  categories,
   onClose,
 }: DecorationItemEditModalProps) {
   const { showToast } = useFeedback();
@@ -72,6 +76,7 @@
           item={item}
           defaultItemType={item.itemType}
           typeTotalCount={typeTotalCount}
+          categories={categories}
           errors={errors}
           itemIdField={
             <FoxInput
app/(protected)/(basic)/decoration-items/_components/decoration-item-form-fields.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-form-fields.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-form-fields.tsx
@@ -9,10 +9,9 @@
 import { FoxTab } from '@fox/core/components/fox-tab';
 import { FoxTabItem } from '@fox/core/components/fox-tab-item';
 import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch';
+import type { CommonCode } from '@/lib/domain/common-code';
 import {
-  DECORATION_ITEM_CATEGORIES,
   DECORATION_ITEM_TYPE_OPTIONS,
-  DEFAULT_DECORATION_ITEM_CATEGORY_CODE,
   formatDecorationItemActiveLabel,
   type DecorationItem,
   type DecorationItemType,
@@ -27,6 +26,8 @@
   defaultItemType: DecorationItemType;
   /** 현재 유형의 전체 등록 건수(검색어 무관) — 정렬순서의 "/ 총 N개"와 기본값에 쓴다. */
   typeTotalCount: number;
+  /** 공통코드 `ITEM_CATE_CD` — 카테고리 선택지. 서버에서 조회해 내려온다. */
+  categories: CommonCode[];
   errors: DecorationItemFormErrors;
   /** 아이템ID 줄 — 등록은 자동발급 안내, 수정은 발급된 값이라 팝업마다 달라 밖에서 받는다. */
   itemIdField: ReactNode;
@@ -54,14 +55,17 @@
   item,
   defaultItemType,
   typeTotalCount,
+  categories,
   errors,
   itemIdField,
 }: DecorationItemFormFieldsProps) {
   const [itemType, setItemType] = useState<DecorationItemType>(
     item?.itemType ?? defaultItemType
   );
+  // 수정 팝업은 기존 값, 등록 팝업은 첫 코드가 기본이다. 코드가 하나도 없으면 빈 값이고,
+  // 그대로 저장하면 Server Action의 검증이 "카테고리를 선택해 주세요."로 잡는다.
   const [categoryCode, setCategoryCode] = useState(
-    item?.categoryCode || DEFAULT_DECORATION_ITEM_CATEGORY_CODE
+    item?.categoryCode || categories[0]?.code || ''
   );
   const [isActive, setIsActive] = useState(item?.isActive ?? true);
   // 수정 팝업에서 파일을 새로 고르지 않았을 때 유지할 기존 이미지. 썸네일을 지우면 함께 비워
@@ -109,10 +113,11 @@
           <FoxSelect
             size="md"
             label="카테고리"
-            options={DECORATION_ITEM_CATEGORIES.map((category) => ({
+            options={categories.map((category) => ({
               value: category.code,
               label: category.label,
             }))}
+            placeholder="카테고리를 선택하세요."
             value={categoryCode}
             onValueChange={setCategoryCode}
             error={Boolean(errors.categoryCode)}
app/(protected)/(basic)/decoration-items/_components/decoration-item-list.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-list.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-list.tsx
@@ -12,6 +12,7 @@
 import { FoxTab } from '@fox/core/components/fox-tab';
 import { FoxTabItem } from '@fox/core/components/fox-tab-item';
 import { FoxPlusIcon } from '@fox/core/icons';
+import type { CommonCode } from '@/lib/domain/common-code';
 import {
   DECORATION_ITEM_TYPE_OPTIONS,
   EMPTY_FIELD_PLACEHOLDER,
@@ -43,6 +44,8 @@
   totalCount: number;
   /** 현재 유형(개별/셋트)의 검색어 무관 전체 건수 — 등록·수정 팝업의 정렬순서 힌트에 쓴다. */
   typeTotalCount: number;
+  /** 공통코드 `ITEM_CATE_CD` — 등록·수정 팝업의 카테고리 선택지. */
+  categories: CommonCode[];
 }
 
 /**
@@ -67,6 +70,7 @@
   totalPages,
   totalCount,
   typeTotalCount,
+  categories,
 }: DecorationItemListProps) {
   const router = useRouter();
   const [isCreateOpen, setIsCreateOpen] = useState(false);
@@ -140,7 +144,11 @@
       header: '관리',
       width: 160,
       render: (row) => (
-        <DecorationItemRowActions item={row} typeTotalCount={typeTotalCount} />
+        <DecorationItemRowActions
+          item={row}
+          typeTotalCount={typeTotalCount}
+          categories={categories}
+        />
       ),
     },
   ];
@@ -226,6 +234,7 @@
         <DecorationItemCreateModal
           defaultItemType={query.itemType}
           typeTotalCount={typeTotalCount}
+          categories={categories}
           onClose={() => setIsCreateOpen(false)}
         />
       )}
app/(protected)/(basic)/decoration-items/_components/decoration-item-row-actions.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-row-actions.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-row-actions.tsx
@@ -6,6 +6,7 @@
 import { FoxIconButton } from '@fox/core/components/fox-icon-button';
 import { FoxPencilSimpleIcon, FoxTrashIcon } from '@fox/core/icons';
 import { useFeedback } from '@/app/_hooks/use-feedback';
+import type { CommonCode } from '@/lib/domain/common-code';
 import type { DecorationItem } from '@/lib/domain/decoration-item';
 import { deleteDecorationItemAction } from '../_actions';
 import { DecorationItemEditModal } from './decoration-item-edit-modal';
@@ -14,6 +15,7 @@
   item: DecorationItem;
   /** 수정 팝업의 "/ 총 N개" 힌트로 그대로 흘려보낸다(현재 유형 전체 등록 건수). */
   typeTotalCount: number;
+  categories: CommonCode[];
 }
 
 /**
@@ -26,6 +28,7 @@
 export function DecorationItemRowActions({
   item,
   typeTotalCount,
+  categories,
 }: DecorationItemRowActionsProps) {
   const { showAlert, hideAlert, showToast } = useFeedback();
   const [isEditOpen, setIsEditOpen] = useState(false);
@@ -84,6 +87,7 @@
         <DecorationItemEditModal
           item={item}
           typeTotalCount={typeTotalCount}
+          categories={categories}
           onClose={() => setIsEditOpen(false)}
         />
       )}
app/(protected)/(basic)/decoration-items/page.tsx
--- app/(protected)/(basic)/decoration-items/page.tsx
+++ app/(protected)/(basic)/decoration-items/page.tsx
@@ -1,6 +1,8 @@
 import type { Metadata } from 'next';
 import { verifySession } from '@/lib/auth/dal';
+import { fetchCommonCodes } from '@/lib/data/repositories/common-code-repository';
 import { fetchDecorationItems } from '@/lib/data/repositories/decoration-item-repository';
+import { CODE_GROUP } from '@/lib/domain/common-code';
 import { parseDecorationItemQuery } from '@/lib/domain/decoration-item-query';
 import { DecorationItemList } from './_components/decoration-item-list';
 
@@ -27,8 +29,12 @@
   await verifySession();
 
   const query = parseDecorationItemQuery(await searchParams);
-  const { items, totalCount, isTotalCountExact, typeTotalCount } =
-    await fetchDecorationItems(query);
+  // 목록과 카테고리는 서로를 기다릴 이유가 없어 함께 띄운다.
+  const [{ items, totalCount, isTotalCountExact, typeTotalCount }, categories] =
+    await Promise.all([
+      fetchDecorationItems(query),
+      fetchCommonCodes(CODE_GROUP.decorationItemCategory),
+    ]);
 
   // 전체 건수가 확정되지 않았다면(백엔드가 count를 주지 않아 하한값만 아는 상태) 다음 페이지를
   // 한 칸 열어 둔다 — 열어 두지 않으면 가득 찬 페이지 뒤의 데이터에 접근할 방법이 없어진다
@@ -48,6 +54,7 @@
       totalPages={totalPages}
       totalCount={totalCount}
       typeTotalCount={typeTotalCount}
+      categories={categories}
     />
   );
 }
 
lib/data/repositories/common-code-repository.ts (added)
+++ lib/data/repositories/common-code-repository.ts
@@ -0,0 +1,71 @@
+import 'server-only';
+import { cache } from 'react';
+import { getSessionAccessToken } from '@/lib/auth/dal';
+import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
+import type { CommonCode } from '@/lib/domain/common-code';
+
+/**
+ * 공통코드 Repository.
+ *
+ * ```
+ * GET /api/v1/mngr/code/list/{comCd}   그룹의 상세코드 목록 (ROLE_ADMIN)
+ * ```
+ *
+ * 백엔드(edupay-backend, develop)의 MngrCodeApiController / MngrCodeMapper.xml을 읽고 확인한 것:
+ *
+ * - 응답은 `TB_SYS_COM_CD_DTL` 행 배열이고, 쓸 값은 **`comDtlCd`(코드)와 `cdNm`(이름)** 둘이다.
+ * - 삭제된 코드(`DEL_YN='Y'`)는 SQL이 이미 걸러 준다.
+ * - **정렬은 백엔드가 준 순서를 그대로 쓴다.** SQL이 `ROW_NUMBER() OVER (ORDER BY FRST_REG_DT
+ *   DESC, SORT_SEQ)`를 다시 `ORDER BY RNUM DESC`로 뒤집어, 결과는 등록일 오름차순 + 정렬순서
+ *   내림차순이다(아이템 목록과 같은 이중 역순 패턴). 화면에서 다시 정렬하면 다른 화면과 순서가
+ *   어긋나므로 손대지 않는다.
+ *
+ * `cache()`로 감싼 이유 — 한 요청 안에서 화면(선택지 그리기)과 Server Action(입력값 검증)이
+ * 같은 그룹을 각각 부른다. 요청 단위로 기억해 두면 왕복이 한 번으로 줄고, 그리는 데 쓴 목록과
+ * 검증에 쓴 목록이 반드시 같아진다.
+ *
+ * 캐시: `no-store` — 코드는 자주 바뀌지 않지만 관리 화면에서 코드를 고치자마자 반영돼야 하고,
+ * 위 `cache()`가 이미 요청 안의 중복 호출을 막는다.
+ */
+export const fetchCommonCodes = cache(async function fetchCommonCodes(
+  groupCode: string
+): Promise<CommonCode[]> {
+  const accessToken = await getSessionAccessToken();
+
+  const result = await backendFetch<unknown>(`/api/v1/mngr/code/list/${groupCode}`, {
+    method: 'GET',
+    accessToken: accessToken ?? undefined,
+    cache: 'no-store',
+  });
+
+  if (!result.ok) {
+    throw new BackendRequestError(result);
+  }
+
+  if (!Array.isArray(result.data)) {
+    throw new Error('공통코드 목록 응답의 형식이 올바르지 않습니다.');
+  }
+
+  return result.data.flatMap(toCommonCode);
+});
+
+/**
+ * 응답 1건 → 도메인 타입. 코드나 이름이 없는 행은 선택지로 쓸 수 없으므로 **예외 대신 건너뛴다**
+ * — 코드 한 줄이 깨졌다고 화면 전체를 못 쓰게 만들 이유가 없다(목록 조회의 fail-fast와 다른
+ * 판단이다. 그쪽은 없으면 화면의 존재 이유가 사라진다).
+ */
+function toCommonCode(raw: unknown): CommonCode[] {
+  if (raw === null || typeof raw !== 'object') {
+    return [];
+  }
+
+  const source = raw as Record<string, unknown>;
+  const code = source.comDtlCd;
+  const label = source.cdNm;
+
+  if (typeof code !== 'string' || code.length === 0) {
+    return [];
+  }
+
+  return [{ code, label: typeof label === 'string' && label ? label : code }];
+}
 
lib/domain/common-code.ts (added)
+++ lib/domain/common-code.ts
@@ -0,0 +1,34 @@
+/**
+ * 공통코드 도메인 — 순수 데이터 표현, 외부 의존 없음.
+ *
+ * 백엔드 `TB_SYS_COM_CD_DTL`의 한 그룹(`COM_CD`)에 속한 상세코드 목록이며,
+ * `GET /api/v1/mngr/code/list/{comCd}`(ROLE_ADMIN)로 가져온다.
+ */
+
+export type CommonCode = {
+  /** 백엔드 `comDtlCd` — 저장·전송에 쓰는 코드값. */
+  code: string;
+  /** 백엔드 `cdNm` — 화면에 보이는 이름. */
+  label: string;
+};
+
+/** 코드 그룹 ID — 화면이 문자열을 직접 적지 않도록 여기 모은다. */
+export const CODE_GROUP = {
+  /** 꾸미기 아이템 카테고리. */
+  decorationItemCategory: 'ITEM_CATE_CD',
+} as const;
+
+/** 코드값 → 이름. 목록에 없는 코드는 코드값 자체를 보여준다(이름을 지어내지 않는다). */
+export function formatCommonCode(
+  codes: readonly CommonCode[],
+  code: string
+): string {
+  return codes.find((item) => item.code === code)?.label ?? code;
+}
+
+export function isKnownCommonCode(
+  codes: readonly CommonCode[],
+  code: string
+): boolean {
+  return codes.some((item) => item.code === code);
+}
lib/domain/decoration-item-form.ts
--- lib/domain/decoration-item-form.ts
+++ lib/domain/decoration-item-form.ts
@@ -13,8 +13,8 @@
  * **이 파일이 검증의 단일 진실원천이다.** Server Action(`_actions.ts`)이 저장 직전에 여기를 거친다.
  */
 
+import { isKnownCommonCode, type CommonCode } from '@/lib/domain/common-code';
 import {
-  DECORATION_ITEM_CATEGORIES,
   DECORATION_ITEM_TYPE_OPTIONS,
   type DecorationItemType,
 } from '@/lib/domain/decoration-item';
@@ -69,10 +69,6 @@
   return DECORATION_ITEM_TYPE_OPTIONS.some((option) => option.value === value);
 }
 
-function isDecorationItemCategoryCode(value: string): boolean {
-  return DECORATION_ITEM_CATEGORIES.some((category) => category.code === value);
-}
-
 /**
  * 등록·수정 공통 검증. 오류는 넘겨받은 객체에 채워 넣고, 정규화된 값을 돌려준다.
  *
@@ -83,6 +79,7 @@
  */
 function validateEditableValues(
   values: DecorationItemEditableValues,
+  categories: readonly CommonCode[],
   errors: DecorationItemFormErrors
 ): DecorationItemEditableValues {
   const name = values.name.trim();
@@ -98,7 +95,7 @@
     errors.name = `아이템명은 ${NAME_MAX_LENGTH}자 이내로 입력해 주세요.`;
   }
 
-  if (!isDecorationItemCategoryCode(values.categoryCode)) {
+  if (!isKnownCommonCode(categories, values.categoryCode)) {
     errors.categoryCode = '카테고리를 선택해 주세요.';
   }
 
@@ -123,12 +120,18 @@
   return { ...values, name, description };
 }
 
-/** 시안 ADM_ITM_102_p — 등록 검증. */
+/**
+ * 시안 ADM_ITM_102_p — 등록 검증.
+ *
+ * 허용 카테고리는 상수가 아니라 **인자로 받는다** — 값이 백엔드 공통코드(`ITEM_CATE_CD`)에서
+ * 오므로 이 파일이 알 수 없고, 알아서도 안 된다(domain 계층은 통신을 하지 않는다).
+ */
 export function validateDecorationItemCreate(
-  values: DecorationItemEditableValues
+  values: DecorationItemEditableValues,
+  categories: readonly CommonCode[]
 ): ValidationResult<DecorationItemEditableValues> {
   const errors: DecorationItemFormErrors = {};
-  const editable = validateEditableValues(values, errors);
+  const editable = validateEditableValues(values, categories, errors);
 
   if (Object.keys(errors).length > 0) {
     return { ok: false, errors };
@@ -139,7 +142,8 @@
 
 /** 시안 ADM_ITM_103_p — 수정 검증. 아이템ID는 읽기 전용이라 검증 대상이 아니다. */
 export function validateDecorationItemUpdate(
-  values: DecorationItemEditableValues
+  values: DecorationItemEditableValues,
+  categories: readonly CommonCode[]
 ): ValidationResult<DecorationItemEditableValues> {
-  return validateDecorationItemCreate(values);
+  return validateDecorationItemCreate(values, categories);
 }
lib/domain/decoration-item.ts
--- lib/domain/decoration-item.ts
+++ lib/domain/decoration-item.ts
@@ -26,27 +26,6 @@
 
 export const DEFAULT_DECORATION_ITEM_TYPE: DecorationItemType = 'individual';
 
-/**
- * 카테고리 — **임시 값이다.** 백엔드는 공통코드테이블(`TB_SYS_COM_CD_DTL`, `COM_CD='ITEM_CATE_CD'`)을
- * 조인해 `itemCateNm`을 내려주지만 그 코드가 아직 정비되지 않아, 사용자 지시에 따라 프론트에서
- * 임의 코드로 개발한다(시안 등록 팝업의 예시값 계절/축하/시즌을 그대로 씀).
- *
- * **백엔드에 코드가 추가되면 이 상수를 지우고 `GET /api/v1/common/code/ITEM_CATE_CD`(인증 불필요)
- * 응답으로 교체한다.** 그때까지 저장되는 `itemCateCd`는 여기 정의된 임시 코드라, 실제 코드 체계가
- * 정해지면 기존 데이터의 코드값 마이그레이션이 필요하다.
- */
-export const DECORATION_ITEM_CATEGORIES: ReadonlyArray<{
-  code: string;
-  label: string;
-}> = [
-  { code: 'CATE01', label: '계절' },
-  { code: 'CATE02', label: '축하' },
-  { code: 'CATE03', label: '시즌' },
-];
-
-export const DEFAULT_DECORATION_ITEM_CATEGORY_CODE =
-  DECORATION_ITEM_CATEGORIES[0].code;
-
 export type DecorationItem = {
   /**
    * 백엔드 `itemSn` — 자동증가 PK이자 화면의 "아이템ID"로 그대로 노출하는 값(사용자 확정 사항).
@@ -56,12 +35,9 @@
   itemSn: number;
   itemType: DecorationItemType;
   name: string;
-  /** 백엔드 `itemCateCd` — 저장·전송에 쓰는 코드값. */
+  /** 백엔드 `itemCateCd` — 저장·전송에 쓰는 코드값(공통코드 `ITEM_CATE_CD`의 상세코드). */
   categoryCode: string;
-  /**
-   * 백엔드 `itemCateNm` — 공통코드테이블 조인 결과. 코드가 코드테이블에 없으면 null로 온다
-   * (지금은 임시 코드를 쓰므로 대개 null이다 — 화면은 `formatDecorationItemCategory`로 보완한다).
-   */
+  /** 백엔드 `itemCateNm` — 공통코드테이블 조인 결과. 코드가 코드테이블에 없으면 null이다. */
   categoryName: string | null;
   /** 백엔드 `itemAmount` — 오픈 가능한 포인트. */
   points: number;
@@ -93,19 +69,11 @@
 }
 
 /**
- * 카테고리 표기 — 백엔드가 코드테이블에서 찾은 이름을 우선 쓰고, 없으면 프론트 임시 목록에서
- * 찾고, 그것도 없으면 코드값 자체를 보여준다. 임시 코드 단계에서는 두 번째 경로가 주로 쓰이고,
- * 백엔드 코드가 정비되면 자연스럽게 첫 번째 경로로 넘어간다.
+ * 카테고리 표기 — 목록 SQL이 코드테이블을 조인해 내려 준 이름을 쓴다. 코드가 코드테이블에서
+ * 지워졌거나 값 자체가 비었을 때만 코드값·`-`로 떨어진다(이름을 지어내지 않는다).
  */
 export function formatDecorationItemCategory(item: DecorationItem): string {
-  if (item.categoryName) {
-    return item.categoryName;
-  }
-
-  const known = DECORATION_ITEM_CATEGORIES.find(
-    (category) => category.code === item.categoryCode
-  );
-  return known?.label ?? item.categoryCode ?? EMPTY_FIELD_PLACEHOLDER;
+  return item.categoryName || item.categoryCode || EMPTY_FIELD_PLACEHOLDER;
 }
 
 /** 수정일시 표기 — 백엔드가 이미 `YYYY-MM-DD`로 포맷해 주므로 그대로 쓰고 null만 보완한다. */
Add a comment
List