summaryrefslogtreecommitdiff
path: root/lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx')
-rw-r--r--lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx364
1 files changed, 364 insertions, 0 deletions
diff --git a/lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx b/lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx
new file mode 100644
index 00000000..36d47403
--- /dev/null
+++ b/lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx
@@ -0,0 +1,364 @@
+"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, Settings2, Wand2, Copy, Eye } from "lucide-react"
+import { toast } from "sonner"
+import { cn } from "@/lib/utils"
+
+import { updateGtcClauseSchema, type UpdateGtcClauseSchema } from "@/lib/gtc-contract/gtc-clauses/validations"
+import { updateGtcClause } from "@/lib/gtc-contract/gtc-clauses/service"
+import { type GtcClauseTreeView } from "@/db/schema/gtc"
+import { useSession } from "next-auth/react"
+
+interface ClauseVariableSettingsDialogProps
+ extends React.ComponentPropsWithRef<typeof Dialog> {
+ clause: GtcClauseTreeView | null
+ onSuccess?: () => void
+}
+
+export function ClauseVariableSettingsDialog({
+ clause,
+ onSuccess,
+ ...props
+}: ClauseVariableSettingsDialogProps) {
+ const [isUpdatePending, startUpdateTransition] = React.useTransition()
+ const [showPreview, setShowPreview] = React.useState(false)
+ const { data: session } = useSession()
+
+ const currentUserId = React.useMemo(() => {
+ return session?.user?.id ? Number(session.user.id) : null
+ }, [session])
+
+ const form = useForm<UpdateGtcClauseSchema>({
+ resolver: zodResolver(updateGtcClauseSchema),
+ defaultValues: {
+ numberVariableName: "",
+ subtitleVariableName: "",
+ contentVariableName: "",
+ editReason: "",
+ },
+ })
+
+ // clause가 변경될 때 폼 데이터 설정
+ React.useEffect(() => {
+ if (clause) {
+ form.reset({
+ numberVariableName: clause.numberVariableName || "",
+ subtitleVariableName: clause.subtitleVariableName || "",
+ contentVariableName: clause.contentVariableName || "",
+ editReason: "",
+ })
+ }
+ }, [clause, form])
+
+ const generateAutoVariableNames = () => {
+ if (!clause) return
+
+ const fullPath = clause.fullPath || clause.itemNumber
+
+ console.log(clause.fullPath,fullPath,"fullPath")
+ console.log(clause, "clause")
+
+ const prefix = "CLAUSE_" + fullPath.replace(/\./g, "_")
+
+ form.setValue("numberVariableName", `${prefix}_NUMBER`)
+ form.setValue("subtitleVariableName", `${prefix}_SUBTITLE`)
+ form.setValue("contentVariableName", `${prefix}_CONTENT`)
+
+ toast.success("변수명이 자동 생성되었습니다.")
+ }
+
+ const copyCurrentVariableNames = () => {
+ if (!clause) return
+
+ const currentVars = {
+ number: clause.autoNumberVariable,
+ subtitle: clause.autoSubtitleVariable,
+ content: clause.autoContentVariable,
+ }
+
+ form.setValue("numberVariableName", currentVars.number)
+ form.setValue("subtitleVariableName", currentVars.subtitle)
+ form.setValue("contentVariableName", currentVars.content)
+
+ toast.success("현재 변수명이 복사되었습니다.")
+ }
+
+ async function onSubmit(data: UpdateGtcClauseSchema) {
+ startUpdateTransition(async () => {
+ if (!clause || !currentUserId) {
+ toast.error("조항 정보를 찾을 수 없습니다.")
+ return
+ }
+
+ try {
+ const result = await updateGtcClause(clause.id, {
+ numberVariableName: data.numberVariableName,
+ subtitleVariableName: data.subtitleVariableName,
+ contentVariableName: data.contentVariableName,
+ editReason: data.editReason || "PDFTron 변수명 설정",
+ updatedById: 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()
+ setShowPreview(false)
+ }
+ props.onOpenChange?.(nextOpen)
+ }
+
+ const currentNumberVar = form.watch("numberVariableName")
+ const currentSubtitleVar = form.watch("subtitleVariableName")
+ const currentContentVar = form.watch("contentVariableName")
+
+ const hasAllVariables = currentNumberVar && currentSubtitleVar && currentContentVar
+
+ if (!clause) {
+ return null
+ }
+
+ return (
+ <Dialog {...props} onOpenChange={handleDialogOpenChange}>
+ <DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
+ <DialogHeader className="flex-shrink-0">
+ <DialogTitle className="flex items-center gap-2">
+ <Settings2 className="h-5 w-5" />
+ PDFTron 변수명 설정
+ </DialogTitle>
+ <DialogDescription>
+ 조항의 PDFTron 변수명을 설정하여 문서 생성에 사용합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ {/* 조항 정보 */}
+ <div className="p-3 bg-muted/50 rounded-lg text-sm flex-shrink-0">
+ <div className="font-medium mb-2">대상 조항</div>
+ <div className="space-y-1 text-muted-foreground">
+ <div className="flex items-center gap-2">
+ <Badge variant="outline">{clause.itemNumber}</Badge>
+ <span>{clause.subtitle}</span>
+ <Badge variant={clause.hasAllVariableNames ? "default" : "destructive"}>
+ {clause.hasAllVariableNames ? "설정됨" : "미설정"}
+ </Badge>
+ </div>
+ {clause.fullPath && (
+ <div className="text-xs">경로: {clause.fullPath}</div>
+ )}
+ {clause.category && (
+ <div className="text-xs">분류: {clause.category}</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">
+ {/* 자동 생성 버튼들 */}
+ <div className="flex gap-2">
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={generateAutoVariableNames}
+ className="flex items-center gap-2"
+ >
+ <Wand2 className="h-4 w-4" />
+ 자동 생성
+ </Button>
+
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={copyCurrentVariableNames}
+ className="flex items-center gap-2"
+ >
+ <Copy className="h-4 w-4" />
+ 현재값 복사
+ </Button>
+
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={() => setShowPreview(!showPreview)}
+ className="flex items-center gap-2"
+ >
+ <Eye className="h-4 w-4" />
+ {showPreview ? "미리보기 숨기기" : "미리보기"}
+ </Button>
+ </div>
+
+ {/* 현재 설정된 변수명 표시 */}
+ {clause.hasAllVariableNames && (
+ <div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
+ <div className="text-sm font-medium text-blue-900 mb-2">현재 설정된 변수명</div>
+ <div className="space-y-1 text-xs">
+ <div><code className="bg-blue-100 px-1 rounded">{clause.numberVariableName}</code></div>
+ <div><code className="bg-blue-100 px-1 rounded">{clause.subtitleVariableName}</code></div>
+ <div><code className="bg-blue-100 px-1 rounded">{clause.contentVariableName}</code></div>
+ </div>
+ </div>
+ )}
+
+ {/* 변수명 입력 필드들 */}
+ <div className="space-y-4">
+ <FormField
+ control={form.control}
+ name="numberVariableName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>채번 변수명 *</FormLabel>
+ <FormControl>
+ <Input
+ placeholder="예: CLAUSE_1_NUMBER, HEADER_1_NUM 등"
+ {...field}
+ />
+ </FormControl>
+ <FormDescription>
+ 문서에서 조항 번호를 표시할 변수명입니다.
+ </FormDescription>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="subtitleVariableName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>소제목 변수명 *</FormLabel>
+ <FormControl>
+ <Input
+ placeholder="예: CLAUSE_1_SUBTITLE, HEADER_1_TITLE 등"
+ {...field}
+ />
+ </FormControl>
+ <FormDescription>
+ 문서에서 조항 제목을 표시할 변수명입니다.
+ </FormDescription>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="contentVariableName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>상세항목 변수명 *</FormLabel>
+ <FormControl>
+ <Input
+ placeholder="예: CLAUSE_1_CONTENT, BODY_1_TEXT 등"
+ {...field}
+ />
+ </FormControl>
+ <FormDescription>
+ 문서에서 조항 내용을 표시할 변수명입니다.
+ </FormDescription>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 미리보기 */}
+ {showPreview && hasAllVariables && (
+ <div className="p-3 bg-gray-50 border rounded-lg">
+ <div className="text-sm font-medium mb-2">PDFTron 템플릿 미리보기</div>
+ <div className="space-y-2 text-xs font-mono bg-white p-2 rounded border">
+ <div className="text-blue-600">{"{{" + currentNumberVar + "}}"}. {"{{" + currentSubtitleVar + "}}"}</div>
+ <div className="text-gray-600 ml-4">{"{{" + currentContentVar + "}}"}</div>
+ </div>
+ <div className="text-xs text-muted-foreground mt-2">
+ 실제 문서에서 위와 같은 형태로 표시됩니다.
+ </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={isUpdatePending}
+ >
+ Cancel
+ </Button>
+ <Button
+ type="submit"
+ disabled={isUpdatePending || !hasAllVariables}
+ >
+ {isUpdatePending && (
+ <Loader
+ className="mr-2 size-4 animate-spin"
+ aria-hidden="true"
+ />
+ )}
+ <Settings2 className="mr-2 h-4 w-4" />
+ Save Variables
+ </Button>
+ </DialogFooter>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ )
+} \ No newline at end of file