diff options
| author | 0-Zz-ang <s1998319@gmail.com> | 2025-11-19 09:50:12 +0900 |
|---|---|---|
| committer | 0-Zz-ang <s1998319@gmail.com> | 2025-11-19 09:50:12 +0900 |
| commit | 2f1bef8eeff5d6cd30c4de808402893deb35335d (patch) | |
| tree | b3a286bca25df3a038ace20969855b8485878b7b /lib/compliance/questions/compliance-question-edit-sheet.tsx | |
| parent | 84277bd79bd6a2bff0f6ef6840f1790db06036e6 (diff) | |
준법설문조사 리비전관리
Diffstat (limited to 'lib/compliance/questions/compliance-question-edit-sheet.tsx')
| -rw-r--r-- | lib/compliance/questions/compliance-question-edit-sheet.tsx | 232 |
1 files changed, 185 insertions, 47 deletions
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<string | null>(null); + const previousQuestionIdRef = React.useRef<number | null>(null); const form = useForm<QuestionFormData>({ 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({ /> </div> + {/* 레드플래그 체크박스 */} + <FormField + control={form.control} + name="isRedFlag" + render={({ field }) => ( + <FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4 bg-red-50"> + <FormControl> + <Checkbox + checked={field.value || false} + onCheckedChange={(checked) => { + // 이벤트 전파 방지 + field.onChange(checked); + }} + onClick={(e) => { + // 클릭 이벤트 전파 방지 + e.stopPropagation(); + }} + /> + </FormControl> + <div className="space-y-1 leading-none"> + <FormLabel className="text-red-700">레드플래그 질문</FormLabel> + <FormDescription> + 질문 유형 - RADIO || 옵션 - YES/NO + </FormDescription> + </div> + </FormItem> + )} + /> + <FormField control={form.control} name="questionText" @@ -326,9 +456,13 @@ export function ComplianceQuestionEditSheet({ <FormItem> <FormLabel>질문 유형</FormLabel> <Select + value={field.value || ""} onValueChange={(newValue) => { + if (isRedFlag) return; // 레드플래그일 때는 변경 불가 const currentType = field.value; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const isCurrentSelectionType = [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((currentType || "").toUpperCase() as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const isNewSelectionType = [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes(newValue.toUpperCase() as any); // 선택형에서 비선택형으로 변경하고 기존 옵션이 있는 경우 @@ -339,7 +473,7 @@ export function ComplianceQuestionEditSheet({ field.onChange(newValue); } }} - defaultValue={(field.value || "").toUpperCase()} + disabled={isRedFlag} > <FormControl> <SelectTrigger> @@ -363,25 +497,27 @@ export function ComplianceQuestionEditSheet({ <div className="space-y-3"> <div className="flex items-center justify-between"> <div className="text-sm font-medium">옵션 관리</div> - <Button - type="button" - variant="outline" - size="sm" - onClick={() => { - setNewOptionValue(""); - setNewOptionText(""); - setNewOptionOther(false); - // 옵션 추가 모드 활성화 - setShowOptionForm(true); - }} - > - <Plus className="h-4 w-4 mr-1" /> - 옵션 추가 - </Button> + {!isRedFlag && ( + <Button + type="button" + variant="outline" + size="sm" + onClick={() => { + setNewOptionValue(""); + setNewOptionText(""); + setNewOptionOther(false); + // 옵션 추가 모드 활성화 + setShowOptionForm(true); + }} + > + <Plus className="h-4 w-4 mr-1" /> + 옵션 추가 + </Button> + )} </div> {/* 옵션 추가 폼 */} - {showOptionForm && ( + {showOptionForm && !isRedFlag && ( <div className="space-y-3 p-3 border rounded-lg bg-muted/50"> <div className="grid grid-cols-2 gap-3"> <div> @@ -467,23 +603,25 @@ export function ComplianceQuestionEditSheet({ <div className="text-sm font-mono">{opt.optionValue}</div> <div className="text-sm flex-1">{opt.optionText}</div> {opt.allowsOtherInput && <Badge variant="secondary">기타 허용</Badge>} - <Button - type="button" - variant="ghost" - size="icon" - onClick={async () => { - try { - await deleteComplianceQuestionOption(opt.id); - await loadOptions(); - toast.success("옵션이 삭제되었습니다."); - } catch (e) { - console.error(e); - toast.error("옵션 삭제 중 오류가 발생했습니다."); - } - }} - > - <Trash2 className="h-4 w-4" /> - </Button> + {!isRedFlag && ( + <Button + type="button" + variant="ghost" + size="icon" + onClick={async () => { + try { + await deleteComplianceQuestionOption(opt.id); + await loadOptions(); + toast.success("옵션이 삭제되었습니다."); + } catch (e) { + console.error(e); + toast.error("옵션 삭제 중 오류가 발생했습니다."); + } + }} + > + <Trash2 className="h-4 w-4" /> + </Button> + )} </div> )) )} |
