"use client"; import * as React from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Plus, Trash2 } from "lucide-react"; import { createComplianceQuestion, createComplianceQuestionOption, getComplianceQuestionsCount, getComplianceQuestions, getComplianceQuestionOptions } from "@/lib/compliance/services"; import { QUESTION_TYPES } from "@/db/schema/compliance"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; const questionSchema = z.object({ questionNumber: z.string().min(1, "질문 번호를 입력하세요"), questionText: z.string().min(1, "질문 내용을 입력하세요"), questionType: z.string().min(1, "질문 유형을 선택하세요"), isRequired: z.boolean(), isConditional: z.boolean(), hasDetailText: z.boolean(), hasFileUpload: z.boolean(), conditionalValue: z.string().optional(), }).refine((data) => { // 필수 질문이거나 조건부 질문이어야 함 return data.isRequired || data.isConditional; }, { message: "필수 질문 또는 조건부 질문 중 하나는 선택해야 합니다.", path: ["isRequired", "isConditional"] }); type QuestionFormData = z.infer; interface ComplianceQuestionCreateDialogProps { templateId: number; onSuccess?: () => void; } export function ComplianceQuestionCreateDialog({ templateId, onSuccess }: ComplianceQuestionCreateDialogProps) { const [open, setOpen] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false); const router = useRouter(); const form = useForm({ resolver: zodResolver(questionSchema), defaultValues: { questionNumber: "", questionText: "", questionType: "", isRequired: false, isConditional: false, hasDetailText: false, hasFileUpload: false, conditionalValue: "", }, }); // 부모 질문 및 옵션 상태 const [parentQuestionId, setParentQuestionId] = React.useState(""); const [selectableParents, setSelectableParents] = React.useState>([]); const [parentOptions, setParentOptions] = React.useState>([]); // 옵션 관리 상태 const [options, setOptions] = React.useState>([]); const [newOptionValue, setNewOptionValue] = React.useState(""); const [newOptionText, setNewOptionText] = React.useState(""); const [newOptionOther, setNewOptionOther] = React.useState(false); const [showOptionForm, setShowOptionForm] = React.useState(false); // 선택형 질문인지 확인 const isSelectionType = React.useMemo(() => { const questionType = form.watch("questionType"); return [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((questionType || "").toUpperCase() as any); }, [form.watch("questionType")]); // 시트/다이얼로그 열릴 때 부모 후보 로드 (같은 템플릿 내 선택형 질문만) React.useEffect(() => { if (!open) return; (async () => { try { const qs = await getComplianceQuestions(templateId); const filtered = (qs || []).filter((q: any) => [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((q.questionType || "").toUpperCase())); setSelectableParents(filtered); } catch (e) { console.error("load selectable parents error", e); } })(); }, [open, templateId]); // 부모 선택 시 옵션 로드 React.useEffect(() => { if (!open) return; (async () => { if (!parentQuestionId) { setParentOptions([]); return; } try { const opts = await getComplianceQuestionOptions(Number(parentQuestionId)); setParentOptions(opts.map((o: any) => ({ id: o.id, optionValue: o.optionValue, optionText: o.optionText }))); } catch (e) { console.error("load parent options error", e); setParentOptions([]); } })(); }, [open, parentQuestionId]); const onSubmit = async (data: QuestionFormData) => { try { setIsLoading(true); // 새로운 질문의 displayOrder는 기존 질문 개수 + 1 const currentQuestionsCount = await getComplianceQuestionsCount(templateId); // 디버깅을 위한 로그 console.log("Form data:", data); console.log("isConditional:", form.watch("isConditional")); console.log("parentQuestionId:", parentQuestionId); console.log("Final parentQuestionId:", form.watch("isConditional") && parentQuestionId ? Number(parentQuestionId) : null); const newQuestion = await createComplianceQuestion({ templateId, ...data, parentQuestionId: form.watch("isConditional") && parentQuestionId ? Number(parentQuestionId) : null, displayOrder: currentQuestionsCount + 1, }); // 선택형 질문이고 옵션이 있다면 옵션들도 생성 if (isSelectionType && options.length > 0 && newQuestion) { try { // 옵션들을 순차적으로 생성 for (let i = 0; i < options.length; i++) { const option = options[i]; await createComplianceQuestionOption({ questionId: newQuestion.id, optionValue: option.optionValue, optionText: option.optionText, allowsOtherInput: option.allowsOtherInput, displayOrder: i + 1, }); } } catch (optionError) { console.error("Error creating options:", optionError); toast.error("질문은 생성되었지만 옵션 생성 중 오류가 발생했습니다."); } } toast.success("질문이 성공적으로 추가되었습니다."); setOpen(false); form.reset(); setOptions([]); setShowOptionForm(false); // 페이지 새로고침 router.refresh(); if (onSuccess) { onSuccess(); } } catch (error) { console.error("Error creating question:", error); // 중복 질문번호 오류 처리 if (error instanceof Error && error.message === "DUPLICATE_QUESTION_NUMBER") { form.setError("questionNumber", { type: "manual", message: "이미 사용 중인 질문번호입니다." }); toast.error("이미 사용 중인 질문번호입니다."); } else { toast.error("질문 추가 중 오류가 발생했습니다."); } } finally { setIsLoading(false); } }; return ( 새 질문 추가 템플릿에 새로운 질문을 추가합니다.
( 질문 번호 )} /> {/* 필수 질문과 조건부 질문 체크박스 */}
(
필수 질문 응답자가 반드시 답변해야 하는 질문
)} /> (
조건부 질문 특정 조건에 따라 표시되는 질문
)} />
{/* 유효성 검사 에러 메시지 */} {(form.formState.errors.isRequired || form.formState.errors.isConditional) && (
{form.formState.errors.isRequired?.message || form.formState.errors.isConditional?.message || "필수 질문 또는 조건부 질문 중 하나는 선택해야 합니다."}
)} ( 질문 내용