File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'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 { Select } from '@/components/ui/select';
import { useFeedback } from '@/app/_hooks/use-feedback';
import {
ANSWER_STATUS_OPTIONS,
formatInquiryTypeLabel,
formatOptionalValue,
type BoardPost,
} from '@/lib/domain/board-post';
import {
ANSWER_MAX_LENGTH,
INITIAL_INQUIRY_ANSWER_FORM_STATE,
} from '@/lib/domain/board-post-form';
import { saveInquiryAnswerAction } from '../../_actions';
import { BoardAttachmentField } from '../../_components/board-attachment-field';
import { BoardContentField } from '../../_components/board-content-field';
import { FieldError } from '../../_components/board-post-form-modal';
interface InquiryAnswerModalProps {
post: BoardPost;
onClose: () => void;
}
const FORM_ID = 'inquiry-answer-form';
/**
* 1:1문의 상세 및 답변 팝업 — 시안 A_BOA_013_p.
*
* 질문자가 작성한 항목(질문유형·제목·질문자명·작성일·이메일·전화번호·질문내용)은 **전부 읽기
* 전용**이고, 관리자가 바꾸는 것은 진행상태·답변내용·답변첨부 셋뿐이다. 그래서 읽기 전용 값은
* 폼에 싣지 않는다 — Server Action도 그 값들을 읽지 않으므로 위조해 보내도 반영되지 않는다.
*
* 전화번호는 **마스킹하지 않고 원본을 보여준다** — 목록에서는 마스킹하지만(시안 A_BOA_012),
* 상세는 답변을 위해 연락처를 확인하는 화면이라 시안에도 원본(`010-1234-5678`)으로 표기돼 있다.
*
* 첨부는 **답변 첨부만** 다룬다(사용자 확인 사항). 백엔드 필드가 `atchFileId` 하나뿐이라 질문
* 첨부와 공유되며, 시안에도 "질문 첨부파일 → 일단 삭제" 메모가 있다.
*
* 시안 메모의 "답변완료 시 사용자에게 앱푸쉬 전송"은 **백엔드 미구현**이다 — 게시판 서비스의 푸시
* 분기가 빈 블록이라 프론트가 할 수 있는 일이 없다. 별도로 보고한 사항이다.
*/
export function InquiryAnswerModal({ post, onClose }: InquiryAnswerModalProps) {
const { showToast } = useFeedback();
const [state, formAction, isPending] = useActionState(
saveInquiryAnswerAction,
INITIAL_INQUIRY_ANSWER_FORM_STATE
);
useEffect(() => {
if (state.status === 'success') {
showToast({ variant: 'success', message: '답변을 저장했습니다.' });
onClose();
}
}, [state, showToast, onClose]);
const errors = state.status === 'error' ? (state.errors ?? {}) : {};
return (
<Modal
title="1:1문의 상세 및 답변"
onClose={onClose}
footer={
<>
<Button type="button" variant="ghost" onClick={onClose}>
취소
</Button>
<Button
type="submit"
form={FORM_ID}
variant="primary"
disabled={isPending}
>
{isPending ? '저장 중...' : '저장'}
</Button>
</>
}
>
<form id={FORM_ID} action={formAction} className="flex flex-col gap-4">
<input type="hidden" name="id" value={post.id} />
<Field label="질문유형">
<Input value={formatInquiryTypeLabel(post.typeCode)} readOnly />
</Field>
<p className="text-body-sm text-foreground-muted">
아래 질문 항목은 조회 전용입니다.
</p>
<Field label="질문제목">
<Input value={formatOptionalValue(post.title)} readOnly />
</Field>
<div className="flex gap-2">
<Field label="질문자명" className="flex-1">
<Input value={formatOptionalValue(post.authorName)} readOnly />
</Field>
<Field label="작성일" className="flex-1">
<Input value={formatOptionalValue(post.createdAt)} readOnly />
</Field>
</div>
<div className="flex gap-2">
<Field label="이메일" className="flex-1">
<Input value={formatOptionalValue(post.authorEmail)} readOnly />
</Field>
<Field label="전화번호" className="flex-1">
<Input value={formatOptionalValue(post.authorPhoneNumber)} readOnly />
</Field>
</div>
<Field label="질문내용">
<textarea
value={post.content}
readOnly
rows={5}
className="w-full cursor-default rounded-md border border-border bg-surface-muted px-3 py-2 text-body-md text-foreground"
/>
</Field>
<Field label="진행상태 *">
<Select
name="answerStatusCode"
defaultValue={post.answerStatusCode ?? ANSWER_STATUS_OPTIONS[0].value}
>
{ANSWER_STATUS_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</Field>
<p className="text-body-sm text-foreground-muted">
답변완료를 선택한 경우에만 사용자 화면에 답변 내용이 노출됩니다.
</p>
<FieldError message={errors.answerStatusCode} />
<BoardContentField
name="answerContent"
label="답변내용"
defaultValue={post.answerContent ?? ''}
maxLength={ANSWER_MAX_LENGTH}
placeholder="답변 내용을 입력하세요."
/>
<FieldError message={errors.answerContent} />
<BoardAttachmentField
label="답변 첨부파일"
attachmentId={post.attachmentId}
/>
{state.status === 'error' && state.message && (
<p className="text-body-sm text-danger">{state.message}</p>
)}
</form>
</Modal>
);
}