"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover" import { Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, } from "@/components/ui/command" import { Check, ChevronsUpDown, Loader, Copy } from "lucide-react" import { cn } from "@/lib/utils" import { toast } from "sonner" import { cloneGtcDocumentSchema, type CloneGtcDocumentSchema } from "@/lib/gtc-contract/validations" import { cloneGtcDocument, getAvailableProjectsForGtcExcluding, hasStandardGtcDocument } from "@/lib/gtc-contract/service" import { type ProjectForFilter } from "@/lib/gtc-contract/service" import { type GtcDocumentWithRelations } from "@/db/schema/gtc" import { useSession } from "next-auth/react" import { Input } from "@/components/ui/input" import { useRouter } from "next/navigation" interface CloneGtcDocumentDialogProps { sourceDocument: GtcDocumentWithRelations open?: boolean onOpenChange?: (open: boolean) => void } export function CloneGtcDocumentDialog({ sourceDocument, open: controlledOpen, onOpenChange: controlledOnOpenChange }: CloneGtcDocumentDialogProps) { const [internalOpen, setInternalOpen] = React.useState(false) const [projects, setProjects] = React.useState([]) const [isClonePending, startCloneTransition] = React.useTransition() const { data: session } = useSession() const router = useRouter() const [defaultType, setDefaultType] = React.useState<"standard" | "project">("standard") const isControlled = controlledOpen !== undefined const open = isControlled ? controlledOpen! : internalOpen const setOpen = isControlled ? controlledOnOpenChange! : setInternalOpen const currentUserId = React.useMemo(() => { return session?.user?.id ? Number(session.user.id) : null }, [session]) const form = useForm({ resolver: zodResolver(cloneGtcDocumentSchema), defaultValues: { sourceDocumentId: sourceDocument.id, type: sourceDocument.type, projectId: sourceDocument.projectId, title: sourceDocument.title || "", editReason: "", }, }) const resetForm = React.useCallback((type: "standard" | "project") => { form.reset({ sourceDocumentId: sourceDocument.id, type, projectId: sourceDocument.projectId, title: sourceDocument.title || "", editReason: "", }) }, [form, sourceDocument]) React.useEffect(() => { if (open) { // 표준 GTC 존재 여부와 사용 가능한 프로젝트 동시 조회 Promise.all([ hasStandardGtcDocument(), getAvailableProjectsForGtcExcluding(sourceDocument.projectId || undefined) ]).then(([hasStandard, availableProjects]) => { const initialType = hasStandard ? "project" : "standard" setDefaultType(initialType) setProjects(availableProjects) // 폼 기본값 설정: 원본 문서 타입을 우선으로 하되, 표준이 이미 있고 원본도 표준이면 프로젝트로 변경 const targetType = hasStandard && sourceDocument.type === "standard" ? "project" : sourceDocument.type resetForm(targetType) }) } }, [open, sourceDocument.projectId, sourceDocument.type, resetForm]) const watchedType = form.watch("type") React.useEffect(() => { // 소스 문서가 변경되면 폼 기본값 업데이트 (다이얼로그가 열려있을 때만) if (open) { hasStandardGtcDocument().then((hasStandard) => { const targetType = hasStandard && sourceDocument.type === "standard" ? "project" : sourceDocument.type resetForm(targetType) }) } }, [sourceDocument, resetForm, open]) async function onSubmit(data: CloneGtcDocumentSchema) { startCloneTransition(async () => { if (!currentUserId) { toast.error("로그인이 필요합니다") return } try { const result = await cloneGtcDocument({ ...data, createdById: currentUserId }) if (result.error) { toast.error(`에러: ${result.error}`) return } resetForm(sourceDocument.type) setOpen(false) router.refresh() toast.success("GTC 문서가 복제되었습니다.") } catch (error) { toast.error("문서 복제 중 오류가 발생했습니다.") } }) } function handleDialogOpenChange(nextOpen: boolean) { if (!nextOpen) { // 다이얼로그 닫을 때는 원본 문서 정보로 리셋 resetForm(sourceDocument.type) } setOpen(nextOpen) } const DialogWrapper = isControlled ? React.Fragment : Dialog return ( {!isControlled && ( )} GTC 문서 복제 기존 문서를 복제하여 새로운 문서를 생성합니다.
원본: {sourceDocument.title || `${sourceDocument.type === 'standard' ? '표준' : '프로젝트'} GTC v${sourceDocument.revision}`}
{/* 구분 (Type) */} ( 구분 {defaultType === "project" && sourceDocument.type === "standard" && ( 표준 GTC 문서가 이미 존재합니다. 복제시에는 프로젝트 타입을 권장합니다. )} )} /> {/* 프로젝트 선택 (프로젝트 타입인 경우만) */} {watchedType === "project" && ( { const selectedProject = projects.find( (p) => p.id === field.value ) const [popoverOpen, setPopoverOpen] = React.useState(false) return ( 프로젝트 {projects.length === 0 ? "사용 가능한 프로젝트가 없습니다." : "프로젝트를 찾을 수 없습니다." } {projects.map((project) => { const label = `${project.name} (${project.code})` return ( { field.onChange(project.id) setPopoverOpen(false) }} > {label} ) })} ) }} /> )} ( GTC 제목 (선택사항) 워드의 제목으로 사용됩니다. )} /> {/* 편집 사유 */} ( 복제 사유 (선택사항)