summaryrefslogtreecommitdiff
path: root/lib/compliance/questions/compliance-questions-draggable-list.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compliance/questions/compliance-questions-draggable-list.tsx')
-rw-r--r--lib/compliance/questions/compliance-questions-draggable-list.tsx157
1 files changed, 157 insertions, 0 deletions
diff --git a/lib/compliance/questions/compliance-questions-draggable-list.tsx b/lib/compliance/questions/compliance-questions-draggable-list.tsx
new file mode 100644
index 00000000..6a226b54
--- /dev/null
+++ b/lib/compliance/questions/compliance-questions-draggable-list.tsx
@@ -0,0 +1,157 @@
+"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="outline">{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>
+ );
+}