diff options
Diffstat (limited to 'lib/gtc-contract/gtc-clauses/table/clause-variable-settings-dialog.tsx')
| -rw-r--r-- | lib/gtc-contract/gtc-clauses/table/clause-variable-settings-dialog.tsx | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/lib/gtc-contract/gtc-clauses/table/clause-variable-settings-dialog.tsx b/lib/gtc-contract/gtc-clauses/table/clause-variable-settings-dialog.tsx new file mode 100644 index 00000000..36d47403 --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/table/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 |
