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, useRef, useState } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
import { FoxInput } from '@fox/core/components/fox-input';
import { FoxModal } from '@fox/core/components/fox-modal';
import { FoxSelect } from '@fox/core/components/fox-select';
import { FoxTextArea } from '@fox/core/components/fox-text-area';
import { submitFormAction } from '@/app/_hooks/submit-form-action';
import { useFeedback } from '@/app/_hooks/use-feedback';
import {
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 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;
}
// 백엔드가 「답변완료」로 처음 저장될 때만 학습자에게 푸시를 보낸다
// (MngrBbsServiceImpl.update — 이전 답변자가 없을 때 1회). 관리자가 그 사실을 알아야
// 상태를 고르는 판단이 선다.
const STATUS_HELP_TEXT =
'답변완료 선택 시에만 FO(사용자 화면)에 답변 내용 노출, 목록에 답변일도 표시. 처음 답변완료로 저장할 때 학습자에게 알림이 발송됩니다.';
/**
* 이슈: 기획의 "답변완료 시 앱푸쉬 전송"은 백엔드 푸시 분기가 빈 블록이라 동작하지 않는다.
* 이슈: 백엔드 첨부 필드가 `atchFileId` 하나뿐이라 질문 첨부와 답변 첨부가 같은 자리를 쓴다.
*/
export function InquiryAnswerModal({
post,
answerStatuses,
onClose,
}: InquiryAnswerModalProps) {
const { showToast } = useFeedback();
const formRef = useRef<HTMLFormElement>(null);
const [statusCode, setStatusCode] = useState(
post.answerStatusCode ?? answerStatuses[0]?.code ?? ''
);
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 (
<FoxModal
dismissible={false}
open
size="md"
title="1:1문의 상세 및 답변"
onClose={onClose}
actions={
<>
<FoxButton type="default" size="md" label="취소" onAction={onClose} />
<FoxButton
type="primary"
size="md"
label="저장"
loading={isPending}
onAction={() => formRef.current?.requestSubmit()}
/>
</>
}
>
<form
ref={formRef}
onSubmit={(event) => submitFormAction(event, formAction)}
className={styles.fields}
>
<input type="hidden" name="id" value={post.id} />
<div className={styles.field}>
<FoxInput
size="md"
label="질문유형"
value={post.typeName ?? formatInquiryTypeLabel(post.typeCode)}
readOnly
/>
</div>
<p className={styles.sectionTitle}>게시물의 항목 (조회 전용)</p>
<div className={styles.field}>
<div className={styles.pair}>
<FoxInput
size="md"
label="질문자명"
value={formatOptionalValue(post.authorName)}
readOnly
/>
<FoxInput
size="md"
label="작성일"
value={formatOptionalValue(post.createdAt)}
readOnly
/>
</div>
</div>
<div className={styles.field}>
<div className={styles.pair}>
<FoxInput
size="md"
label="이메일"
value={formatOptionalValue(post.authorEmail)}
readOnly
/>
<FoxInput
size="md"
label="전화번호"
value={formatOptionalValue(post.authorPhoneNumber)}
readOnly
/>
</div>
</div>
<div className={styles.field}>
<FoxTextArea
label="질문내용"
value={post.content}
readOnly
rows={4}
/>
</div>
<div className={styles.field}>
<FoxSelect
size="md"
label="진행상태"
requirement="required"
options={answerStatuses.map((status) => ({
value: status.code,
label: status.label,
}))}
value={statusCode}
onValueChange={setStatusCode}
error={Boolean(errors.answerStatusCode)}
hint={errors.answerStatusCode ?? STATUS_HELP_TEXT}
/>
<input type="hidden" name="answerStatusCode" value={statusCode} />
</div>
<div className={styles.field}>
<FoxTextArea
name="answerContent"
label="답변내용"
defaultValue={post.answerContent ?? ''}
maxLength={ANSWER_MAX_LENGTH}
counter
rows={4}
placeholder="답변 내용을 입력하세요."
invalid={Boolean(errors.answerContent)}
message={errors.answerContent}
/>
</div>
<div className={styles.field}>
<BoardAttachmentField
label="답변 첨부파일"
attachmentId={post.attachmentId}
/>
</div>
{state.status === 'error' && state.message && (
<FoxHelperText type="danger" message={state.message} />
)}
</form>
</FoxModal>
);
}