1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
"use client";
import * as React from "react";
import { Badge } from "@/components/ui/badge";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Sortable, SortableDragHandle, SortableItem } from "@/components/ui/sortable";
import { GripVertical } from "lucide-react";
import { complianceQuestions } from "@/db/schema/compliance";
import { ComplianceQuestionEditSheet } from "./compliance-question-edit-sheet";
import { ComplianceQuestionDeleteDialog } from "./compliance-question-delete-dialog";
import { updateComplianceQuestion } from "@/lib/compliance/services";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
interface SortableQuestionItemProps {
question: typeof complianceQuestions.$inferSelect;
onSuccess?: () => void;
}
function SortableQuestionItem({ question, onSuccess }: SortableQuestionItemProps) {
return (
<SortableItem value={question.id} className="mb-1">
<AccordionItem value={`question-${question.id}`}>
<AccordionTrigger className="text-left py-1.5">
<div className="flex items-center gap-2 w-full">
<SortableDragHandle
variant="ghost"
size="sm"
className="p-0.5 h-auto hover:bg-muted/50 rounded"
>
<GripVertical className="h-3 w-3 text-muted-foreground" />
</SortableDragHandle>
<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} />
<ComplianceQuestionDeleteDialog question={question} onSuccess={onSuccess} />
</div>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="space-y-4 pt-2 pl-8">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="font-medium">질문 타입:</span>
<Badge variant="secondary" className="ml-2">{question.questionType}</Badge>
</div>
<div>
<span className="font-medium">필수 여부:</span>
<Badge variant="secondary" className="ml-2">
{question.isRequired ? '필수' : '선택'}
</Badge>
</div>
<div>
<span className="font-medium">상세 설명:</span>
<Badge variant="secondary" className="ml-2">
{question.hasDetailText ? '필요' : '불필요'}
</Badge>
</div>
<div>
<span className="font-medium">파일 업로드:</span>
<Badge variant="secondary" className="ml-2">
{question.hasFileUpload ? '필요' : '불필요'}
</Badge>
</div>
</div>
{question.conditionalValue && (
<div className="text-sm text-muted-foreground">
<span className="font-medium">조건:</span> {question.conditionalValue}
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
</SortableItem>
);
}
interface ComplianceQuestionsDraggableListProps {
questions: typeof complianceQuestions.$inferSelect[];
onSuccess?: () => void;
}
export function ComplianceQuestionsDraggableList({
questions,
onSuccess
}: ComplianceQuestionsDraggableListProps) {
const [items, setItems] = React.useState(questions);
const router = useRouter();
React.useEffect(() => {
setItems(questions);
}, [questions]);
const handleValueChange = async (newItems: typeof complianceQuestions.$inferSelect[]) => {
setItems(newItems);
// 새로운 순서로 displayOrder 업데이트
const updatedItems = newItems.map((item, index) => ({
...item,
displayOrder: index + 1,
}));
// 서버에 순서 업데이트
await updateDisplayOrders(updatedItems);
};
const updateDisplayOrders = async (updatedItems: typeof complianceQuestions.$inferSelect[]) => {
try {
// 각 질문의 displayOrder를 순차적으로 업데이트
await Promise.all(
updatedItems.map((item, index) =>
updateComplianceQuestion(item.id, {
displayOrder: index + 1,
})
)
);
toast.success("질문 순서가 업데이트되었습니다.");
router.refresh();
if (onSuccess) {
onSuccess();
}
} catch (error) {
console.error("Error updating question order:", error);
toast.error("질문 순서 업데이트 중 오류가 발생했습니다.");
}
};
if (items.length === 0) {
return (
<div className="text-center py-8 text-muted-foreground">
아직 질문이 없습니다. 질문을 추가해보세요.
</div>
);
}
return (
<Sortable value={items} onValueChange={handleValueChange}>
<Accordion type="single" collapsible className="w-full">
{items.map((question) => (
<SortableQuestionItem
key={question.id}
question={question}
onSuccess={onSuccess}
/>
))}
</Accordion>
</Sortable>
);
}
|