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 { useState, useActionState } from 'react';
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
import { FoxInput } from '@fox/core/components/fox-input';
import { FoxPageHeader } from '@fox/core/components/fox-page-header';
import { FoxRadio } from '@fox/core/components/fox-radio';
import { FoxRadioGroup } from '@fox/core/components/fox-radio-group';
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 {
ANSWER_MESSAGE_ETC,
ANSWER_MESSAGE_OPTIONS,
QUIZ_ANSWER_OPTIONS,
QUIZ_LEVEL_OPTIONS,
keywordsFromText,
type QuizItem,
type GradesBySchool,
} from '@/lib/domain/content';
import {
ANSWER_MESSAGE_MAX_LENGTH,
INITIAL_CONTENT_FORM_STATE,
MESSAGE_MAX_LENGTH,
QUESTION_MAX_LENGTH,
} from '@/lib/domain/content-form';
import { contentBreadcrumb, contentListPath } from '@/lib/domain/content-query';
import type { CommonCode } from '@/lib/domain/common-code';
import { createQuizAction, updateQuizAction } from '../../_actions';
import { ContentFormActions } from '../../_components/content-form-actions';
import { ContentFormRow } from '../../_components/content-form-row';
import { ContentGradeField } from '../../_components/content-grade-field';
import { ContentKeywordField } from '../../_components/content-keyword-field';
import { ContentVisibilityField } from '../../_components/content-visibility-field';
import styles from '../../_components/content-form.module.scss';
interface QuizFormProps {
schools: readonly CommonCode[];
gradesBySchool: GradesBySchool;
item?: QuizItem;
}
/**
* 콘텐츠 등록·수정 (금융OX퀴즈) — 시안 5402:12360.
*
* 정답여부는 O·X 두 줄이고, 고른 쪽에서만 「사용자에게 노출할 문구」를 정한다(기획 ③).
* 「기타」를 고르면 문구를 직접 적는다.
*/
export function QuizForm({ schools, gradesBySchool, item }: QuizFormProps) {
const isEdit = item !== undefined;
const [state, formAction, isPending] = useActionState(
isEdit ? updateQuizAction : createQuizAction,
INITIAL_CONTENT_FORM_STATE
);
const [answer, setAnswer] = useState(item?.answer || QUIZ_ANSWER_OPTIONS[0]);
const [messageCode, setMessageCode] = useState(ANSWER_MESSAGE_OPTIONS[0].value);
const [messageText, setMessageText] = useState('');
const errors = state.status === 'error' ? (state.errors ?? {}) : {};
return (
<>
<FoxPageHeader
title={`콘텐츠 ${isEdit ? '수정' : '등록'} (금융OX퀴즈)`}
breadcrumb={contentBreadcrumb('quiz', isEdit ? '수정' : '등록')}
/>
<form
onSubmit={(event) => submitFormAction(event, formAction)}
className={styles.form}
>
{isEdit && <input type="hidden" name="quizSn" value={item.quizSn} />}
{/* 화면에 입력 칸이 없는 값이라 그대로 돌려보내 지워지지 않게 한다(도메인 주석 참조). */}
<input type="hidden" name="quizPoint" value={item?.points ?? ''} />
{isEdit && (
<ContentFormRow label="콘텐츠ID">
<FoxInput size="md" value={item.quizSn} readOnly />
</ContentFormRow>
)}
<ContentFormRow label="학년선택" required>
<ContentGradeField
schools={schools}
gradesBySchool={gradesBySchool}
defaultSchoolCode={item?.schoolCode}
defaultGrades={item?.grades}
schoolError={errors.schoolCode}
gradesError={errors.grades}
/>
</ContentFormRow>
<ContentFormRow label="난이도" required>
<FoxRadioGroup name="level" size="md" label="난이도" orientation="horizontal">
{QUIZ_LEVEL_OPTIONS.map((option) => (
<FoxRadio
key={option.value}
value={option.value}
label={option.label}
defaultChecked={(item?.level || QUIZ_LEVEL_OPTIONS[1].value) === option.value}
/>
))}
</FoxRadioGroup>
{errors.level && <FoxHelperText type="danger" message={errors.level} />}
</ContentFormRow>
<ContentFormRow label="키워드">
<ContentKeywordField
defaultKeywords={
item ? keywordsFromText(item.keywordText) : undefined
}
error={errors.keywords}
/>
</ContentFormRow>
<ContentFormRow label="질문" required>
<FoxTextArea
name="question"
aria-label="질문"
defaultValue={item?.question ?? ''}
rows={3}
placeholder="질문을 입력하세요."
maxLength={QUESTION_MAX_LENGTH}
counter
invalid={Boolean(errors.question)}
message={errors.question}
/>
</ContentFormRow>
<ContentFormRow label="정답여부" required>
<div className={styles.answerLines}>
{QUIZ_ANSWER_OPTIONS.map((option) => (
<div key={option} className={styles.inline}>
<FoxRadio
name="answer"
size="md"
value={option}
label={option}
checked={answer === option}
onChange={setAnswer}
/>
<div className={styles.answerMessage}>
<FoxSelect
size="md"
options={ANSWER_MESSAGE_OPTIONS.map((message) => ({
value: message.value,
label: message.label,
}))}
value={messageCode}
onValueChange={setMessageCode}
disabled={answer !== option}
/>
</div>
{messageCode === ANSWER_MESSAGE_ETC && (
<div className={styles.answerMessage}>
<FoxInput
size="md"
aria-label="노출할 문구"
value={messageText}
onChange={setMessageText}
placeholder="내용을 입력하세요."
maxLength={ANSWER_MESSAGE_MAX_LENGTH}
disabled={answer !== option}
/>
</div>
)}
</div>
))}
</div>
<input type="hidden" name="answerMessageCode" value={messageCode} />
<input type="hidden" name="answerMessageText" value={messageText} />
{(errors.answer || errors.answerMessageText) && (
<FoxHelperText
type="danger"
message={errors.answer ?? errors.answerMessageText ?? ''}
/>
)}
</ContentFormRow>
<ContentFormRow label="해설" required>
<FoxTextArea
name="explanation"
aria-label="해설"
defaultValue={item?.explanation ?? ''}
rows={4}
placeholder="해설(전달 메시지)을 입력하세요."
maxLength={MESSAGE_MAX_LENGTH}
counter
invalid={Boolean(errors.explanation)}
message={errors.explanation}
/>
</ContentFormRow>
<ContentFormRow label="공개 설정" required>
<ContentVisibilityField defaultVisible={item?.isVisible ?? false} />
</ContentFormRow>
{state.status === 'error' && state.message && (
<FoxHelperText type="danger" message={state.message} />
)}
<ContentFormActions
submitLabel={isEdit ? '수정' : '등록'}
listHref={contentListPath('quiz')}
pending={isPending}
/>
</form>
</>
);
}