From 2f1bef8eeff5d6cd30c4de808402893deb35335d Mon Sep 17 00:00:00 2001
From: 0-Zz-ang <s1998319@gmail.com>
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/compliance-question-edit-sheet.tsx')

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>
                     ))
                   )}
-- 
cgit v1.2.3

