summaryrefslogtreecommitdiff
path: root/lib/compliance/questions
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compliance/questions')
-rw-r--r--lib/compliance/questions/compliance-question-edit-sheet.tsx232
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>
))
)}