diff options
| author | 0-Zz-ang <s1998319@gmail.com> | 2025-08-22 13:47:37 +0900 |
|---|---|---|
| committer | 0-Zz-ang <s1998319@gmail.com> | 2025-08-22 13:47:37 +0900 |
| commit | fefca6304eefea94f41057f9f934b0e19ceb54bb (patch) | |
| tree | f4914faa83e242a68d27feac58ebf0c527302cd2 /lib/compliance/questions/compliance-questions-draggable-list.tsx | |
| parent | dbdae213e39b82ff8ee565df0774bd2f72f06140 (diff) | |
(박서영)Compliance 설문/응답 리스트 생성
Diffstat (limited to 'lib/compliance/questions/compliance-questions-draggable-list.tsx')
| -rw-r--r-- | lib/compliance/questions/compliance-questions-draggable-list.tsx | 157 |
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> + ); +} |
