'use client'; import { useActionState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Field } from '@/components/ui/field'; import { Input } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { RadioGroup } from '@/components/ui/radio-group'; import { Select } from '@/components/ui/select'; import { useFeedback } from '@/app/_hooks/use-feedback'; import { BOARD_CATEGORY_OPTIONS, formatBoardCategoryLabel, formatOptionalValue, type BoardPost, type BoardType, } from '@/lib/domain/board-post'; import { CONTENT_MAX_LENGTH, INITIAL_BOARD_POST_FORM_STATE, TITLE_MAX_LENGTH, } from '@/lib/domain/board-post-form'; import { BOARD_TYPE_OPTIONS } from '@/lib/domain/board-post-query'; import { createBoardPostAction, updateBoardPostAction } from '../_actions'; import { BoardAttachmentField } from './board-attachment-field'; import { BoardContentField } from './board-content-field'; interface BoardPostFormModalProps { boardType: BoardType; mode: 'create' | 'edit'; /** 수정 모드의 기존 값. 등록 모드는 넘기지 않는다. */ post?: BoardPost; onClose: () => void; } const FORM_ID = 'board-post-form'; const PINNED_OPTIONS = [ { value: 'N', label: '사용안함' }, { value: 'Y', label: '사용' }, ]; const VISIBILITY_OPTIONS = [ { value: 'Y', label: '노출' }, { value: 'N', label: '미노출' }, ]; const PUSH_OPTIONS = [ { value: 'Y', label: '발송' }, { value: 'N', label: '미발송' }, ]; const BOARD_TITLES: Record = { notice: '공지사항', inquiry: '1:1문의', faq: 'FAQ', }; /** * 공지사항·FAQ의 등록/수정 팝업 — 시안 A_BOA_007_p·008_p(공지사항), 015_p·016_p(FAQ). * * 네 시안이 같은 폼의 변형이라 한 컴포넌트가 게시판 종류(`boardType`)와 모드(`mode`)로 분기한다. * 게시판별 차이는 두 가지뿐이다 — 공지사항에만 상단고정·노출기간·앱푸쉬가, FAQ에만 유형이 있다. * * **수정 시 구분은 읽기 전용이다**(시안 ①). 값 자체를 제출하지 않고, Server Action도 수정 * 경로에서는 구분을 검증하지 않는다 — 직접 POST로 다른 값을 보내도 반영되지 않는다. * * 첨부파일이 있어 폼은 multipart로 제출된다(Server Action은 이를 자동으로 처리한다). * 저장 버튼은 footer에서 `form={FORM_ID}`로 연결한다 — 네이티브 HTML의 `form` 속성은 버튼이 * DOM상 form의 자손이 아니어도 같은 문서 안에서 id만 일치하면 그 form을 제출한다. */ export function BoardPostFormModal({ boardType, mode, post, onClose, }: BoardPostFormModalProps) { const { showToast } = useFeedback(); const isCreate = mode === 'create'; const [state, formAction, isPending] = useActionState( isCreate ? createBoardPostAction : updateBoardPostAction, INITIAL_BOARD_POST_FORM_STATE ); useEffect(() => { if (state.status === 'success') { showToast({ variant: 'success', message: isCreate ? '게시물을 등록했습니다.' : '게시물을 수정했습니다.', }); onClose(); } }, [state, showToast, onClose, isCreate]); const errors = state.status === 'error' ? (state.errors ?? {}) : {}; const typeOptions = BOARD_TYPE_OPTIONS[boardType]; const hasNoticeOnlyFields = boardType === 'notice'; return ( } >
{!isCreate && post && }

* 는 필수 항목입니다.

{isCreate ? ( ) : ( )} {typeOptions.length > 0 && ( <> )} {hasNoticeOnlyFields && ( <>
~
)} {hasNoticeOnlyFields && ( )} {!isCreate && post && (
)} {state.status === 'error' && state.message && (

{state.message}

)}
); } /** 필드 하단 오류 문구. 값이 없으면 아무것도 그리지 않아 레이아웃을 차지하지 않는다. */ export function FieldError({ message }: { message?: string }) { if (!message) { return null; } return

{message}

; }