임동욱 임동욱 08-20
merge: 1:1문의 상태 공통코드 적용
@284acf965819d36241345272d9ef5345d5cf8163
app/(protected)/(basic)/boards/_actions.ts
--- app/(protected)/(basic)/boards/_actions.ts
+++ app/(protected)/(basic)/boards/_actions.ts
@@ -2,6 +2,8 @@
 
 import { revalidatePath } from 'next/cache';
 import { verifySession } from '@/lib/auth/dal';
+import { fetchCommonCodes } from '@/lib/data/repositories/common-code-repository';
+import { CODE_GROUP } from '@/lib/domain/common-code';
 import {
   createBoardPost,
   deleteBoardPost,
@@ -236,7 +238,12 @@
       answerContent: readString(formData, 'answerContent'),
       attachmentId,
     },
-    { doneStatusCode: ANSWER_STATUS_DONE }
+    {
+      doneStatusCode: ANSWER_STATUS_DONE,
+      allowedStatusCodes: (
+        await fetchCommonCodes(CODE_GROUP.inquiryAnswerStatus)
+      ).map((code) => code.code),
+    }
   );
   if (!validation.ok) {
     return { status: 'error', errors: validation.errors };
app/(protected)/(basic)/boards/_components/board-post-columns.tsx
--- app/(protected)/(basic)/boards/_components/board-post-columns.tsx
+++ app/(protected)/(basic)/boards/_components/board-post-columns.tsx
@@ -4,6 +4,7 @@
 import { FoxIconButton } from '@fox/core/components/fox-icon-button';
 import { FoxStatusIndicator } from '@fox/core/components/fox-status-indicator';
 import type { FoxListColumn } from '@fox/core/components/fox-list-container';
+import type { CommonCode } from '@/lib/domain/common-code';
 import { FoxPaperclipIcon } from '@fox/core/icons';
 import { formatPhoneNumber } from '@/lib/domain/phone-number';
 import {
@@ -137,7 +138,16 @@
   ];
 }
 
-function inquiryColumns(page: number, pageSize: number): FoxListColumn<BoardPost>[] {
+function inquiryColumns(
+  page: number,
+  pageSize: number,
+  answerStatuses: readonly CommonCode[]
+): FoxListColumn<BoardPost>[] {
+  const statusOptions = answerStatuses.map((code) => ({
+    value: code.code,
+    label: code.label,
+  }));
+
   return [
     numberColumn(page, pageSize),
     {
@@ -172,7 +182,7 @@
       render: (row) => (
         <FoxStatusIndicator
           type={row.answeredAt ? 'success' : 'warning'}
-          label={formatAnswerStatusLabel(row.answerStatusCode)}
+          label={formatAnswerStatusLabel(row.answerStatusCode, statusOptions)}
         />
       ),
     },
@@ -186,7 +196,9 @@
       key: 'actions',
       header: '관리',
       width: 160,
-      render: (row) => <InquiryRowActions post={row} />,
+      render: (row) => (
+        <InquiryRowActions post={row} answerStatuses={answerStatuses} />
+      ),
     },
   ];
 }
@@ -194,9 +206,10 @@
 export function boardPostColumns(
   boardType: BoardType,
   page: number,
-  pageSize: number
+  pageSize: number,
+  answerStatuses: readonly CommonCode[] = []
 ): FoxListColumn<BoardPost>[] {
   if (boardType === 'notice') return noticeColumns(page, pageSize);
   if (boardType === 'faq') return faqColumns(page, pageSize);
-  return inquiryColumns(page, pageSize);
+  return inquiryColumns(page, pageSize, answerStatuses);
 }
app/(protected)/(basic)/boards/_components/board-post-list.tsx
--- app/(protected)/(basic)/boards/_components/board-post-list.tsx
+++ app/(protected)/(basic)/boards/_components/board-post-list.tsx
@@ -10,6 +10,7 @@
 import { FoxChipArea } from '@fox/core/components/fox-chip-area';
 import { FoxArrowsClockwiseIcon, FoxPlusIcon } from '@fox/core/icons';
 import type { BoardPost, BoardType } from '@/lib/domain/board-post';
+import type { CommonCode } from '@/lib/domain/common-code';
 import {
   BOARD_PAGE_SIZE_OPTIONS,
   BOARD_SEARCH_FIELDS,
@@ -36,6 +37,8 @@
   canCreate?: boolean;
   /** 행의 "관리" 열을 화면마다 다르게 그려야 할 때. */
   extraToolbar?: ReactNode;
+  /** 공통코드 `BBS_ANS_CD` — 1:1문의 진행상태 표기·선택지. */
+  answerStatuses?: readonly CommonCode[];
 }
 
 const SORT_OPTIONS = [{ value: 'recent', label: '최근등록순' }];
@@ -51,6 +54,7 @@
   totalCount,
   canCreate = true,
   extraToolbar,
+  answerStatuses,
 }: BoardPostListProps) {
   const router = useRouter();
   const [isCreateOpen, setIsCreateOpen] = useState(false);
@@ -66,7 +70,12 @@
         title={title}
         breadcrumb={breadcrumb}
         caption={title}
-        columns={boardPostColumns(boardType, currentPage, query.pageSize)}
+        columns={boardPostColumns(
+          boardType,
+          currentPage,
+          query.pageSize,
+          answerStatuses
+        )}
         rows={items}
         rowKey={(row) => row.id}
         totalCount={totalCount}
app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
--- app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
+++ app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
@@ -10,7 +10,6 @@
 import { submitFormAction } from '@/app/_hooks/submit-form-action';
 import { useFeedback } from '@/app/_hooks/use-feedback';
 import {
-  ANSWER_STATUS_OPTIONS,
   formatInquiryTypeLabel,
   formatOptionalValue,
   type BoardPost,
@@ -19,12 +18,15 @@
   ANSWER_MAX_LENGTH,
   INITIAL_INQUIRY_ANSWER_FORM_STATE,
 } from '@/lib/domain/board-post-form';
+import type { CommonCode } from '@/lib/domain/common-code';
 import { saveInquiryAnswerAction } from '../../_actions';
 import { BoardAttachmentField } from '../../_components/board-attachment-field';
 import styles from './inquiry-answer-modal.module.scss';
 
 interface InquiryAnswerModalProps {
   post: BoardPost;
+  /** 공통코드 `BBS_ANS_CD` — 진행상태 선택지. */
+  answerStatuses: readonly CommonCode[];
   onClose: () => void;
 }
 
@@ -35,11 +37,15 @@
  * 이슈: 기획의 "답변완료 시 앱푸쉬 전송"은 백엔드 푸시 분기가 빈 블록이라 동작하지 않는다.
  * 이슈: 백엔드 첨부 필드가 `atchFileId` 하나뿐이라 질문 첨부와 답변 첨부가 같은 자리를 쓴다.
  */
-export function InquiryAnswerModal({ post, onClose }: InquiryAnswerModalProps) {
+export function InquiryAnswerModal({
+  post,
+  answerStatuses,
+  onClose,
+}: InquiryAnswerModalProps) {
   const { showToast } = useFeedback();
   const formRef = useRef<HTMLFormElement>(null);
   const [statusCode, setStatusCode] = useState(
-    post.answerStatusCode ?? ANSWER_STATUS_OPTIONS[0].value
+    post.answerStatusCode ?? answerStatuses[0]?.code ?? ''
   );
   const [state, formAction, isPending] = useActionState(
     saveInquiryAnswerAction,
@@ -149,9 +155,9 @@
             size="md"
             label="진행상태"
             requirement="required"
-            options={ANSWER_STATUS_OPTIONS.map((option) => ({
-              value: option.value,
-              label: option.label,
+            options={answerStatuses.map((status) => ({
+              value: status.code,
+              label: status.label,
             }))}
             value={statusCode}
             onValueChange={setStatusCode}
app/(protected)/(basic)/boards/inquiries/_components/inquiry-row-actions.tsx
--- app/(protected)/(basic)/boards/inquiries/_components/inquiry-row-actions.tsx
+++ app/(protected)/(basic)/boards/inquiries/_components/inquiry-row-actions.tsx
@@ -7,11 +7,13 @@
 import { FoxPencilSimpleIcon, FoxTrashIcon } from '@fox/core/icons';
 import { useFeedback } from '@/app/_hooks/use-feedback';
 import type { BoardPost } from '@/lib/domain/board-post';
+import type { CommonCode } from '@/lib/domain/common-code';
 import { deleteBoardPostAction } from '../../_actions';
 import { InquiryAnswerModal } from './inquiry-answer-modal';
 
 interface InquiryRowActionsProps {
   post: BoardPost;
+  answerStatuses: readonly CommonCode[];
 }
 
 /**
@@ -20,7 +22,10 @@
  * 공지사항·FAQ의 `BoardRowActions`와 나눈 이유: 여기서 여는 팝업이 수정 폼이 아니라 **상세 및 답변**
  * 팝업이고(관리자는 질문 글을 수정하지 않는다), 등록 경로도 없다. 삭제만 동작이 같다.
  */
-export function InquiryRowActions({ post }: InquiryRowActionsProps) {
+export function InquiryRowActions({
+  post,
+  answerStatuses,
+}: InquiryRowActionsProps) {
   const { showAlert, hideAlert, showToast } = useFeedback();
   const [isAnswerOpen, setIsAnswerOpen] = useState(false);
   const [isDeleting, startDeleting] = useTransition();
@@ -73,7 +78,11 @@
       />
 
       {isAnswerOpen && (
-        <InquiryAnswerModal post={post} onClose={() => setIsAnswerOpen(false)} />
+        <InquiryAnswerModal
+          post={post}
+          answerStatuses={answerStatuses}
+          onClose={() => setIsAnswerOpen(false)}
+        />
       )}
     </FoxButtonGroup>
   );
app/(protected)/(basic)/boards/inquiries/page.tsx
--- app/(protected)/(basic)/boards/inquiries/page.tsx
+++ app/(protected)/(basic)/boards/inquiries/page.tsx
@@ -1,6 +1,8 @@
 import type { Metadata } from 'next';
 import { verifySession } from '@/lib/auth/dal';
 import { fetchBoardPosts } from '@/lib/data/repositories/board-post-repository';
+import { fetchCommonCodes } from '@/lib/data/repositories/common-code-repository';
+import { CODE_GROUP } from '@/lib/domain/common-code';
 import { parseBoardPostQuery } from '@/lib/domain/board-post-query';
 import { BoardPostList } from '../_components/board-post-list';
 
@@ -16,6 +18,7 @@
 
   const query = parseBoardPostQuery('inquiry', await searchParams);
   const { items, totalCount } = await fetchBoardPosts('inquiry', query);
+  const answerStatuses = await fetchCommonCodes(CODE_GROUP.inquiryAnswerStatus);
 
   const totalPages = Math.max(1, Math.ceil(totalCount / query.pageSize));
   const currentPage = Math.min(query.page, totalPages);
@@ -35,6 +38,7 @@
       totalPages={totalPages}
       totalCount={totalCount}
       canCreate={false}
+      answerStatuses={answerStatuses}
     />
   );
 }
lib/domain/board-post-form.ts
--- lib/domain/board-post-form.ts
+++ lib/domain/board-post-form.ts
@@ -11,7 +11,6 @@
  */
 
 import {
-  ANSWER_STATUS_OPTIONS,
   BOARD_CATEGORY_OPTIONS,
   type BoardType,
 } from '@/lib/domain/board-post';
@@ -178,16 +177,13 @@
  */
 export function validateInquiryAnswerForm(
   values: InquiryAnswerFormValues,
-  options: { doneStatusCode: string }
+  options: { doneStatusCode: string; allowedStatusCodes: readonly string[] }
 ): ValidationResult<InquiryAnswerFormValues, InquiryAnswerFormErrors> {
   const errors: InquiryAnswerFormErrors = {};
 
   const answerContent = values.answerContent.trim();
 
-  const isKnownStatus = ANSWER_STATUS_OPTIONS.some(
-    (option) => option.value === values.answerStatusCode
-  );
-  if (!isKnownStatus) {
+  if (!options.allowedStatusCodes.includes(values.answerStatusCode)) {
     errors.answerStatusCode = '진행상태를 선택해 주세요.';
   }
 
lib/domain/board-post.ts
--- lib/domain/board-post.ts
+++ lib/domain/board-post.ts
@@ -128,13 +128,7 @@
  * 1:1문의 「진행상태」(`ansSttsCd`) — **임시값이다.** 백엔드에 코드 정의가 없어 사용자 확인 후
  * 잠정 코드를 한 곳에 모아 두는 방침으로 정했다. 실제 코드가 나오면 이 표만 교체한다.
  */
-export const ANSWER_STATUS_OPTIONS: readonly CodeOption[] = [
-  { value: 'WAIT', label: '답변대기' },
-  { value: 'ING', label: '처리중' },
-  { value: 'DONE', label: '답변완료' },
-];
-
-/** 답변완료 상태 코드 — "답변일 표시"·"답변 완료 시 푸쉬" 판단이 이 값을 참조한다. */
+// TODO: 답변완료를 가리키는 코드값. BBS_ANS_CD의 실제 값이 확인되면 교체한다.
 export const ANSWER_STATUS_DONE = 'DONE';
 
 /** 코드 → 라벨. 표에 없는 코드는 지어내지 않고 코드 그대로 노출한다(잘못된 라벨보다 낫다). */
@@ -157,8 +151,11 @@
   return formatCode(INQUIRY_TYPE_OPTIONS, code);
 }
 
-export function formatAnswerStatusLabel(code: string | null): string {
-  return formatCode(ANSWER_STATUS_OPTIONS, code);
+export function formatAnswerStatusLabel(
+  code: string | null,
+  options: readonly CodeOption[]
+): string {
+  return formatCode(options, code);
 }
 
 /** 사용여부 표기 — 시안의 「노출 / 미노출」. */
lib/domain/common-code.ts
--- lib/domain/common-code.ts
+++ lib/domain/common-code.ts
@@ -21,6 +21,8 @@
 export const CODE_GROUP = {
   /** 꾸미기 아이템 카테고리. */
   decorationItemCategory: 'ITEM_CATE_CD',
+  /** 1:1문의 진행상태. */
+  inquiryAnswerStatus: 'BBS_ANS_CD',
 } as const;
 
 /** 값이 없는 항목의 화면 표기. */
Add a comment
List