summaryrefslogtreecommitdiff
path: root/lib/compliance/questions
diff options
context:
space:
mode:
author0-Zz-ang <s1998319@gmail.com>2025-11-13 11:36:15 +0900
committer0-Zz-ang <s1998319@gmail.com>2025-11-13 11:36:15 +0900
commita4ceade24d28af0bde985bf750017efc02f053ff (patch)
tree45b03d3e6b5d36a60bea464e37155f0a18e73153 /lib/compliance/questions
parent1cdbf77cabcead1a7f76379d3786ede6f43355fb (diff)
(박서영)준법설문조사 RedFlag관련 요청사항 반영
Diffstat (limited to 'lib/compliance/questions')
-rw-r--r--lib/compliance/questions/compliance-question-create-dialog.tsx114
-rw-r--r--lib/compliance/questions/compliance-questions-draggable-list.tsx7
2 files changed, 97 insertions, 24 deletions
diff --git a/lib/compliance/questions/compliance-question-create-dialog.tsx b/lib/compliance/questions/compliance-question-create-dialog.tsx
index b05c2e0d..4abf1eb2 100644
--- a/lib/compliance/questions/compliance-question-create-dialog.tsx
+++ b/lib/compliance/questions/compliance-question-create-dialog.tsx
@@ -40,12 +40,19 @@ import { QUESTION_TYPES } from "@/db/schema/compliance";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
+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(),
isConditional: z.boolean(),
+ isRedFlag: z.boolean(),
hasDetailText: z.boolean(),
hasFileUpload: z.boolean(),
conditionalValue: z.string().optional(),
@@ -80,6 +87,7 @@ export function ComplianceQuestionCreateDialog({
questionType: "",
isRequired: false,
isConditional: false,
+ isRedFlag: false,
hasDetailText: false,
hasFileUpload: false,
conditionalValue: "",
@@ -98,11 +106,32 @@ export function ComplianceQuestionCreateDialog({
const [newOptionOther, setNewOptionOther] = React.useState(false);
const [showOptionForm, setShowOptionForm] = React.useState(false);
+ const isRedFlag = form.watch("isRedFlag");
+ const isRequired = form.watch("isRequired");
+ const isConditional = form.watch("isConditional");
+ const questionTypeValue = form.watch("questionType");
+
// 선택형 질문인지 확인
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")]);
+ return [QUESTION_TYPES.RADIO, QUESTION_TYPES.CHECKBOX, QUESTION_TYPES.DROPDOWN].includes((questionTypeValue || "").toUpperCase() as any);
+ }, [questionTypeValue]);
+
+ // 레드플래그 선택 시 질문 유형을 RADIO로 자동 설정
+ React.useEffect(() => {
+ if (isRedFlag) {
+ form.setValue("questionType", QUESTION_TYPES.RADIO);
+ }
+ }, [form, isRedFlag]);
+
+ // 레드플래그 선택 시 옵션을 YES/NO로 고정
+ React.useEffect(() => {
+ if (isRedFlag) {
+ setOptions(RED_FLAG_OPTIONS.map((option) => ({ ...option })));
+ setShowOptionForm(false);
+ } else {
+ setOptions([]);
+ }
+ }, [isRedFlag]);
// 시트/다이얼로그 열릴 때 부모 후보 로드 (같은 템플릿 내 선택형 질문만)
React.useEffect(() => {
@@ -283,6 +312,28 @@ export function ComplianceQuestionCreateDialog({
)}
/>
</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}
+ onCheckedChange={field.onChange}
+ />
+ </FormControl>
+ <div className="space-y-1 leading-none">
+ <FormLabel className="text-red-700">레드플래그 질문</FormLabel>
+ <FormDescription>
+ 질문 유형 - RADIO || 옵션 - YES/NO
+ </FormDescription>
+ </div>
+ </FormItem>
+ )}
+ />
{/* 유효성 검사 에러 메시지 */}
{(form.formState.errors.isRequired || form.formState.errors.isConditional) && (
@@ -301,7 +352,7 @@ export function ComplianceQuestionCreateDialog({
<Textarea
placeholder="질문 내용을 입력하세요"
className="min-h-[100px]"
- disabled={!form.watch("isRequired") && !form.watch("isConditional")}
+ disabled={!isRequired && !isConditional}
{...field}
/>
</FormControl>
@@ -316,7 +367,11 @@ export function ComplianceQuestionCreateDialog({
render={({ field }) => (
<FormItem>
<FormLabel>질문 유형</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value} disabled={!form.watch("isRequired") && !form.watch("isConditional")}>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value}
+ disabled={(!isRequired && !isConditional) || isRedFlag}
+ >
<FormControl>
<SelectTrigger>
<SelectValue placeholder="질문 유형을 선택하세요" />
@@ -330,6 +385,11 @@ export function ComplianceQuestionCreateDialog({
))}
</SelectContent>
</Select>
+ {/* {isRedFlag && (
+ <FormDescription className="text-red-600">
+ 레드플래그 질문은 RADIO 유형으로 고정됩니다
+ </FormDescription>
+ )} */}
<FormMessage />
</FormItem>
)}
@@ -337,14 +397,14 @@ export function ComplianceQuestionCreateDialog({
{/* 옵션 관리 (선택형 질문일 때만) */}
{isSelectionType && (
- <div className={`space-y-3 ${(!form.watch("isRequired") && !form.watch("isConditional")) ? "opacity-50 pointer-events-none" : ""}`}>
+ <div className={`space-y-3 ${(!isRequired && !isConditional) ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex items-center justify-between">
<div className="text-sm font-medium">옵션 관리</div>
<Button
type="button"
variant="outline"
size="sm"
- disabled={!form.watch("isRequired") && !form.watch("isConditional")}
+ disabled={(!isRequired && !isConditional) || isRedFlag}
onClick={() => {
setNewOptionValue("");
setNewOptionText("");
@@ -358,7 +418,7 @@ export function ComplianceQuestionCreateDialog({
</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>
@@ -439,29 +499,37 @@ export function ComplianceQuestionCreateDialog({
<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={() => {
- const newOptions = options.filter((_, i) => i !== index);
- setOptions(newOptions);
- toast.success("옵션이 제거되었습니다.");
- }}
- >
- <Trash2 className="h-4 w-4" />
- </Button>
+ {!isRedFlag && (
+ <Button
+ type="button"
+ variant="ghost"
+ size="icon"
+ onClick={() => {
+ const newOptions = options.filter((_, i) => i !== index);
+ setOptions(newOptions.map((item, idx) => ({ ...item, displayOrder: idx + 1 })));
+ toast.success("옵션이 제거되었습니다.");
+ }}
+ >
+ <Trash2 className="h-4 w-4" />
+ </Button>
+ )}
</div>
))
)}
</div>
+
+ {/* {isRedFlag && (
+ <div className="text-xs text-red-600">
+ 레드플래그 질문의 라디오 옵션은 YES/NO로 고정됩니다.
+ </div>
+ )} */}
</div>
)}
{/* 조건부 질문일 때만 부모 질문과 조건값 표시 */}
- {form.watch("isConditional") && (
+ {isConditional && (
<div className="space-y-2">
{/* 조건 질문 선택 */}
<div>
@@ -530,13 +598,13 @@ export function ComplianceQuestionCreateDialog({
type="button"
variant="outline"
onClick={() => setOpen(false)}
- disabled={isLoading}
+ disabled={isLoading || (!isRequired && !isConditional)}
>
취소
</Button>
<Button
type="submit"
- disabled={isLoading || (!form.watch("isRequired") && !form.watch("isConditional"))}
+ disabled={isLoading || (!isRequired && !isConditional)}
>
{isLoading ? "추가 중..." : "질문 추가"}
</Button>
diff --git a/lib/compliance/questions/compliance-questions-draggable-list.tsx b/lib/compliance/questions/compliance-questions-draggable-list.tsx
index 6a226b54..3dd82138 100644
--- a/lib/compliance/questions/compliance-questions-draggable-list.tsx
+++ b/lib/compliance/questions/compliance-questions-draggable-list.tsx
@@ -35,7 +35,12 @@ function SortableQuestionItem({ question, onSuccess }: SortableQuestionItemProps
>
<GripVertical className="h-3 w-3 text-muted-foreground" />
</SortableDragHandle>
- <Badge variant="outline">{question.questionNumber}</Badge>
+ <Badge
+ variant={question.isRedFlag ? "destructive" : "outline"}
+ className={question.isRedFlag ? "bg-red-600 text-white border-red-600" : ""}
+ >
+ {question.questionNumber}
+ </Badge>
<span className="font-medium flex-1 leading-tight">{question.questionText}</span>
<div className="flex items-center gap-2">
<ComplianceQuestionEditSheet question={question} onSuccess={onSuccess} />