From 2f1bef8eeff5d6cd30c4de808402893deb35335d Mon Sep 17 00:00:00 2001 From: 0-Zz-ang Date: Wed, 19 Nov 2025 09:50:12 +0900 Subject: 준법설문조사 리비전관리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../questions/compliance-question-edit-sheet.tsx | 232 ++++++++++++++++----- 1 file changed, 185 insertions(+), 47 deletions(-) (limited to 'lib/compliance/questions') diff --git a/lib/compliance/questions/compliance-question-edit-sheet.tsx b/lib/compliance/questions/compliance-question-edit-sheet.tsx index 4b12e775..d34b3ecc 100644 --- a/lib/compliance/questions/compliance-question-edit-sheet.tsx +++ b/lib/compliance/questions/compliance-question-edit-sheet.tsx @@ -55,11 +55,18 @@ import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { complianceQuestions } from "@/db/schema/compliance"; +type OptionItem = { optionValue: string; optionText: string; allowsOtherInput: boolean; displayOrder: number }; +const RED_FLAG_OPTIONS: OptionItem[] = [ + { optionValue: "YES", optionText: "YES", allowsOtherInput: false, displayOrder: 1 }, + { optionValue: "NO", optionText: "NO", allowsOtherInput: false, displayOrder: 2 }, +]; + const questionSchema = z.object({ questionNumber: z.string().min(1, "질문 번호를 입력하세요"), questionText: z.string().min(1, "질문 내용을 입력하세요"), questionType: z.string().min(1, "질문 유형을 선택하세요"), isRequired: z.boolean(), + isRedFlag: z.boolean(), hasDetailText: z.boolean(), hasFileUpload: z.boolean(), isConditional: z.boolean(), @@ -91,6 +98,7 @@ export function ComplianceQuestionEditSheet({ const [showOptionForm, setShowOptionForm] = React.useState(false); const [showOptionsDeleteDialog, setShowOptionsDeleteDialog] = React.useState(false); const [pendingQuestionTypeChange, setPendingQuestionTypeChange] = React.useState(null); + const previousQuestionIdRef = React.useRef(null); const form = useForm({ resolver: zodResolver(questionSchema), @@ -99,6 +107,7 @@ export function ComplianceQuestionEditSheet({ questionText: question.questionText, questionType: question.questionType, isRequired: question.isRequired, + isRedFlag: question.isRedFlag || false, hasDetailText: question.hasDetailText, hasFileUpload: question.hasFileUpload, isConditional: !!question.parentQuestionId, @@ -107,9 +116,13 @@ export function ComplianceQuestionEditSheet({ }, }); + const isRedFlag = form.watch("isRedFlag"); + const questionTypeValue = form.watch("questionType"); + const isSelectionType = React.useMemo(() => { - return [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((form.getValues("questionType") || "").toUpperCase() as any); - }, [form]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((questionTypeValue || "").toUpperCase() as any); + }, [questionTypeValue]); const loadOptions = React.useCallback(async () => { if (!isSelectionType) return; @@ -121,11 +134,80 @@ export function ComplianceQuestionEditSheet({ } }, [isSelectionType, question.id]); + // 레드플래그 선택 시 질문 유형을 RADIO로 자동 설정 + React.useEffect(() => { + if (isRedFlag && open) { + form.setValue("questionType", QUESTION_TYPES.RADIO); + } + }, [form, isRedFlag, open]); + + // 레드플래그 선택 시 옵션을 YES/NO로 고정 React.useEffect(() => { - if (open) { - loadOptions(); + if (isRedFlag && open && isSelectionType) { + // 레드플래그가 켜지면 옵션을 YES/NO로 설정 + const redFlagOptionsList = RED_FLAG_OPTIONS.map((opt) => ({ + id: 0, // 임시 ID + optionValue: opt.optionValue, + optionText: opt.optionText, + allowsOtherInput: opt.allowsOtherInput, + displayOrder: opt.displayOrder, + })); + setOptions(redFlagOptionsList); + } else if (!isRedFlag && open && isSelectionType) { + // 레드플래그가 꺼지면 기존 옵션을 다시 로드 + void loadOptions(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isRedFlag, open, isSelectionType]); + + // 시트가 열릴 때마다 또는 question.id가 변경될 때만 폼을 원본 데이터로 리셋 + React.useEffect(() => { + // question.id가 변경되거나 시트가 새로 열릴 때만 reset + const shouldReset = open && (previousQuestionIdRef.current !== question.id || previousQuestionIdRef.current === null); + + if (shouldReset) { + previousQuestionIdRef.current = question.id; + + const isRedFlagValue = question.isRedFlag || false; + // 레드플래그가 활성화되어 있으면 질문 유형을 RADIO로 설정 + const questionTypeValue = isRedFlagValue ? QUESTION_TYPES.RADIO : (question.questionType as string); + + form.reset({ + questionNumber: question.questionNumber, + questionText: question.questionText, + questionType: questionTypeValue, + isRequired: question.isRequired, + isRedFlag: isRedFlagValue, + hasDetailText: question.hasDetailText, + hasFileUpload: question.hasFileUpload, + isConditional: !!question.parentQuestionId, + parentQuestionId: question.parentQuestionId || undefined, + conditionalValue: question.conditionalValue || "", + }); + setParentQuestionId(question.parentQuestionId || null); + + // 레드플래그가 활성화되어 있으면 YES/NO 옵션 설정, 아니면 기존 옵션 로드 + if (isRedFlagValue) { + const redFlagOptionsList = RED_FLAG_OPTIONS.map((opt) => ({ + id: 0, // 임시 ID + optionValue: opt.optionValue, + optionText: opt.optionText, + allowsOtherInput: opt.allowsOtherInput, + displayOrder: opt.displayOrder, + })); + setOptions(redFlagOptionsList); + } else { + // loadOptions는 별도로 호출 + if (isSelectionType) { + void loadOptions(); + } + } + } else if (!open) { + // 시트가 닫히면 previousQuestionIdRef를 초기화 + previousQuestionIdRef.current = null; } - }, [open, loadOptions]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, question.id, form, isSelectionType]); // 선택 가능한 부모 질문들 로드 (조건부 질문용) React.useEffect(() => { @@ -153,6 +235,7 @@ export function ComplianceQuestionEditSheet({ } try { const data = await getComplianceQuestionOptions(parentQuestionId); + // eslint-disable-next-line @typescript-eslint/no-explicit-any setParentOptions(data.map((o: any) => ({ id: o.id, optionValue: o.optionValue, optionText: o.optionText }))); } catch (e) { console.error("loadParentOptions error", e); @@ -166,26 +249,44 @@ export function ComplianceQuestionEditSheet({ try { setIsLoading(true); - // 디버깅을 위한 로그 - console.log("Edit form data:", data); - console.log("Current isConditional:", data.isConditional); - console.log("Current parentQuestionId:", parentQuestionId); - console.log("Current conditionalValue:", data.conditionalValue); - + // 조건부 질문 관련 데이터 처리 const updateData = { ...data, - parentQuestionId: data.isConditional ? parentQuestionId : null, - conditionalValue: data.isConditional ? data.conditionalValue : null, + parentQuestionId: data.isConditional ? (parentQuestionId ?? undefined) : undefined, + conditionalValue: data.isConditional ? (data.conditionalValue ?? undefined) : undefined, }; // isConditional은 제거 (스키마에 없음) + // eslint-disable-next-line @typescript-eslint/no-explicit-any delete (updateData as any).isConditional; console.log("Final updateData:", updateData); await updateComplianceQuestion(question.id, updateData); + // 레드플래그가 켜져 있고 질문 유형이 RADIO이면 옵션을 YES/NO로 교체 + if (data.isRedFlag && data.questionType === QUESTION_TYPES.RADIO) { + // 기존 옵션 가져오기 + const existingOptions = await getComplianceQuestionOptions(question.id); + + // 기존 옵션 삭제 + for (const option of existingOptions) { + await deleteComplianceQuestionOption(option.id); + } + + // YES/NO 옵션 추가 + for (const redFlagOption of RED_FLAG_OPTIONS) { + await createComplianceQuestionOption({ + questionId: question.id, + optionValue: redFlagOption.optionValue, + optionText: redFlagOption.optionText, + allowsOtherInput: redFlagOption.allowsOtherInput, + displayOrder: redFlagOption.displayOrder, + }); + } + } + toast.success("질문이 성공적으로 수정되었습니다."); setOpen(false); @@ -301,6 +402,35 @@ export function ComplianceQuestionEditSheet({ /> + {/* 레드플래그 체크박스 */} + ( + + + { + // 이벤트 전파 방지 + field.onChange(checked); + }} + onClick={(e) => { + // 클릭 이벤트 전파 방지 + e.stopPropagation(); + }} + /> + +
+ 레드플래그 질문 + + 질문 유형 - RADIO || 옵션 - YES/NO + +
+
+ )} + /> + 질문 유형