diff options
Diffstat (limited to 'lib/gtc-contract/gtc-clauses/table/duplicate-gtc-clause-dialog.tsx')
| -rw-r--r-- | lib/gtc-contract/gtc-clauses/table/duplicate-gtc-clause-dialog.tsx | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/lib/gtc-contract/gtc-clauses/table/duplicate-gtc-clause-dialog.tsx b/lib/gtc-contract/gtc-clauses/table/duplicate-gtc-clause-dialog.tsx new file mode 100644 index 00000000..cb5ac81d --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/table/duplicate-gtc-clause-dialog.tsx @@ -0,0 +1,372 @@ +"use client" + +import * as React from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Badge } from "@/components/ui/badge" + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form" +import { Loader, Copy, Info } from "lucide-react" +import { toast } from "sonner" + +import { createGtcClauseSchema, type CreateGtcClauseSchema } from "@/lib/gtc-contract/gtc-clauses/validations" +import { createGtcClause } from "@/lib/gtc-contract/gtc-clauses/service" +import { type GtcClauseTreeView } from "@/db/schema/gtc" +import { useSession } from "next-auth/react" + +interface DuplicateGtcClauseDialogProps + extends React.ComponentPropsWithRef<typeof Dialog> { + sourceClause: GtcClauseTreeView | null + onSuccess?: () => void +} + +export function DuplicateGtcClauseDialog({ + sourceClause, + onSuccess, + ...props +}: DuplicateGtcClauseDialogProps) { + const [isCreatePending, startCreateTransition] = React.useTransition() + const { data: session } = useSession() + + const currentUserId = React.useMemo(() => { + return session?.user?.id ? Number(session.user.id) : null + }, [session]) + + + const form = useForm<CreateGtcClauseSchema>({ + resolver: zodResolver(createGtcClauseSchema), + defaultValues: { + documentId: 0, + parentId: null, + itemNumber: "", + category: "", + subtitle: "", + content: "", + sortOrder: 0, + // numberVariableName: "", + // subtitleVariableName: "", + // contentVariableName: "", + editReason: "", + }, + }) + + // sourceClause가 변경될 때 폼 데이터 설정 + React.useEffect(() => { + if (sourceClause) { + // 새로운 채번 생성 (원본에 "_copy" 추가) + const newItemNumber = `${sourceClause.itemNumber}_copy` + + form.reset({ + documentId: sourceClause.documentId, + parentId: sourceClause.parentId, + itemNumber: newItemNumber, + category: sourceClause.category || "", + subtitle: `${sourceClause.subtitle} (복제)`, + content: sourceClause.content || "", + sortOrder:parseFloat(sourceClause.sortOrder) + 0.1, // 원본 바로 다음에 위치 + // numberVariableName: "", + // subtitleVariableName: "", + // contentVariableName: "", + editReason: `조항 복제 (원본: ${sourceClause.itemNumber})`, + }) + + // 자동 변수명 생성 + // generateVariableNames(newItemNumber, sourceClause.parentId) + } + }, [sourceClause, form]) + + // const generateVariableNames = (itemNumber: string, parentId: number | null) => { + // if (!sourceClause) return + + // let fullPath = itemNumber + + // if (parentId && sourceClause.fullPath) { + // const parentPath = sourceClause.fullPath.split('.').slice(0, -1).join('.') + // if (parentPath) { + // fullPath = `${parentPath}.${itemNumber}` + // } + // } + + // const prefix = "CLAUSE_" + fullPath.replace(/\./g, "_") + + // form.setValue("numberVariableName", `${prefix}_NUMBER`) + // form.setValue("subtitleVariableName", `${prefix}_SUBTITLE`) + // form.setValue("contentVariableName", `${prefix}_CONTENT`) + // } + + async function onSubmit(data: CreateGtcClauseSchema) { + startCreateTransition(async () => { + if (!currentUserId) { + toast.error("로그인이 필요합니다") + return + } + + try { + const result = await createGtcClause({ + ...data, + createdById: currentUserId + }) + + if (result.error) { + toast.error(`에러: ${result.error}`) + return + } + + form.reset() + props.onOpenChange?.(false) + toast.success("조항이 복제되었습니다.") + onSuccess?.() + } catch (error) { + toast.error("조항 복제 중 오류가 발생했습니다.") + } + }) + } + + function handleDialogOpenChange(nextOpen: boolean) { + if (!nextOpen) { + form.reset() + } + props.onOpenChange?.(nextOpen) + } + + if (!sourceClause) { + return null + } + + return ( + <Dialog {...props} onOpenChange={handleDialogOpenChange}> + <DialogContent className="max-w-2xl h-[90vh] flex flex-col"> + <DialogHeader className="flex-shrink-0"> + <DialogTitle className="flex items-center gap-2"> + <Copy className="h-5 w-5" /> + 조항 복제 + </DialogTitle> + <DialogDescription> + 기존 조항을 복제하여 새로운 조항을 생성합니다. + </DialogDescription> + </DialogHeader> + + {/* 원본 조항 정보 */} + <div className="p-3 bg-muted/50 rounded-lg text-sm flex-shrink-0"> + <div className="flex items-center gap-2 mb-2"> + <Info className="h-4 w-4 text-muted-foreground" /> + <span className="font-medium">복제할 원본 조항</span> + </div> + <div className="space-y-1 text-muted-foreground"> + <div className="flex items-center gap-2"> + <Badge variant="outline">{sourceClause.itemNumber}</Badge> + <span>{sourceClause.subtitle}</span> + </div> + {sourceClause.category && ( + <div>분류: {sourceClause.category}</div> + )} + {sourceClause.content && ( + <div className="text-xs"> + 내용: {sourceClause.content.substring(0, 100)} + {sourceClause.content.length > 100 && "..."} + </div> + )} + </div> + </div> + + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0"> + <div className="flex-1 overflow-y-auto px-1"> + <div className="space-y-4 py-4"> + {/* 새 채번 */} + <FormField + control={form.control} + name="itemNumber" + render={({ field }) => ( + <FormItem> + <FormLabel>새 채번 *</FormLabel> + <FormControl> + <Input + placeholder="예: 1_copy, 1.1_v2, 2.3.1_new 등" + {...field} + /> + </FormControl> + <FormDescription> + 복제된 조항의 새로운 번호입니다. 원본과 다른 번호를 사용해주세요. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + + {/* 분류 */} + <FormField + control={form.control} + name="category" + render={({ field }) => ( + <FormItem> + <FormLabel>분류</FormLabel> + <FormControl> + <Input + placeholder="분류를 수정하거나 그대로 두세요" + {...field} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 소제목 */} + <FormField + control={form.control} + name="subtitle" + render={({ field }) => ( + <FormItem> + <FormLabel>소제목 *</FormLabel> + <FormControl> + <Input + placeholder="소제목을 수정하세요" + {...field} + /> + </FormControl> + <FormDescription> + 복제 시 "(복제)" 접미사가 자동으로 추가됩니다. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + + {/* 상세항목 */} + <FormField + control={form.control} + name="content" + render={({ field }) => ( + <FormItem> + <FormLabel>상세항목</FormLabel> + <FormControl> + <Textarea + placeholder="내용을 수정하거나 그대로 두세요" + {...field} + rows={6} + /> + </FormControl> + <FormDescription> + 원본 조항의 내용이 복사됩니다. 필요시 수정하세요. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + + {/* PDFTron 변수명 섹션 */} + {/* <div className="space-y-3 p-3 border rounded-lg"> + <div className="flex items-center gap-2"> + <Info className="h-4 w-4 text-muted-foreground" /> + <span className="text-sm font-medium">PDFTron 변수명 (자동 생성)</span> + </div> + + <div className="grid grid-cols-1 gap-3"> + <FormField + control={form.control} + name="numberVariableName" + render={({ field }) => ( + <FormItem> + <FormLabel>채번 변수명</FormLabel> + <FormControl> + <Input {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="subtitleVariableName" + render={({ field }) => ( + <FormItem> + <FormLabel>소제목 변수명</FormLabel> + <FormControl> + <Input {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="contentVariableName" + render={({ field }) => ( + <FormItem> + <FormLabel>상세항목 변수명</FormLabel> + <FormControl> + <Input {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="text-xs text-muted-foreground"> + 새 채번을 기반으로 변수명이 자동 생성됩니다. + </div> + </div> */} + + {/* 편집 사유 */} + <FormField + control={form.control} + name="editReason" + render={({ field }) => ( + <FormItem> + <FormLabel>복제 사유</FormLabel> + <FormControl> + <Textarea + placeholder="조항 복제 사유를 입력하세요..." + {...field} + rows={2} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + </div> + + <DialogFooter className="flex-shrink-0 border-t pt-4"> + <Button + type="button" + variant="outline" + onClick={() => props.onOpenChange?.(false)} + disabled={isCreatePending} + > + Cancel + </Button> + <Button type="submit" disabled={isCreatePending}> + {isCreatePending && ( + <Loader + className="mr-2 size-4 animate-spin" + aria-hidden="true" + /> + )} + <Copy className="mr-2 h-4 w-4" /> + Duplicate + </Button> + </DialogFooter> + </form> + </Form> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
