summaryrefslogtreecommitdiff
path: root/lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx')
-rw-r--r--lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx372
1 files changed, 372 insertions, 0 deletions
diff --git a/lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx b/lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx
new file mode 100644
index 00000000..cb5ac81d
--- /dev/null
+++ b/lib/basic-contract/gtc-vendor/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