"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Loader2, Send, FileText, Clock, Upload, X, Building, User, Calendar } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Switch } from "@/components/ui/switch" import TiptapEditor from "@/components/qna/tiptap-editor" import { canRequestReview, requestReview } from "../service" import { LegalWorksDetailView } from "@/db/schema" type LegalWorkData = LegalWorksDetailView interface RequestReviewDialogProps { open: boolean onOpenChange: (open: boolean) => void work: LegalWorkData | null onSuccess?: () => void } // 검토요청 폼 스키마 const requestReviewSchema = z.object({ // 기본 검토 설정 dueDate: z.string().min(1, "검토 완료 희망일을 선택해주세요"), assignee: z.string().optional(), notificationMethod: z.enum(["email", "internal", "both"]).default("both"), // 법무업무 상세 정보 reviewDepartment: z.enum(["준법문의", "법무검토"]), inquiryType: z.enum(["국내계약", "국내자문", "해외계약", "해외자문"]).optional(), // 공통 필드 title: z.string().min(1, "제목을 선택해주세요"), requestContent: z.string().min(1, "요청내용을 입력해주세요"), // 준법문의 전용 필드 isPublic: z.boolean().default(false), // 법무검토 전용 필드들 contractProjectName: z.string().optional(), contractType: z.string().optional(), contractCounterparty: z.string().optional(), counterpartyType: z.enum(["법인", "개인"]).optional(), contractPeriod: z.string().optional(), contractAmount: z.string().optional(), factualRelation: z.string().optional(), projectNumber: z.string().optional(), shipownerOrderer: z.string().optional(), projectType: z.string().optional(), governingLaw: z.string().optional(), }).refine((data) => { // 법무검토 선택시 문의종류 필수 if (data.reviewDepartment === "법무검토" && !data.inquiryType) { return false; } return true; }, { message: "법무검토 선택시 문의종류를 선택해주세요", path: ["inquiryType"] }); type RequestReviewFormValues = z.infer export function RequestReviewDialog({ open, onOpenChange, work, onSuccess }: RequestReviewDialogProps) { const [isSubmitting, setIsSubmitting] = React.useState(false) const [attachments, setAttachments] = React.useState([]) const [editorContent, setEditorContent] = React.useState("") const [canRequest, setCanRequest] = React.useState(true) const [requestCheckMessage, setRequestCheckMessage] = React.useState("") const [isCustomTitle, setIsCustomTitle] = React.useState(false) // work의 category에 따라 기본 reviewDepartment 결정 const getDefaultReviewDepartment = () => { return work?.category === "CP" ? "준법문의" : "법무검토" } const form = useForm({ resolver: zodResolver(requestReviewSchema), defaultValues: { dueDate: "", assignee: "", notificationMethod: "both", reviewDepartment: getDefaultReviewDepartment(), title: getDefaultReviewDepartment() === "준법문의" ? "CP검토" : "GTC검토", requestContent: "", isPublic: false, }, }) // work 변경시 검토요청 가능 여부 확인 React.useEffect(() => { if (work && open) { canRequestReview(work.id).then((result) => { setCanRequest(result.canRequest) setRequestCheckMessage(result.reason || "") }) const defaultDepartment = work.category === "CP" ? "준법문의" : "법무검토" form.setValue("reviewDepartment", defaultDepartment) } }, [work, open, form]) // 검토부문 감시 const reviewDepartment = form.watch("reviewDepartment") const inquiryType = form.watch("inquiryType") const titleValue = form.watch("title") // 조건부 필드 활성화 로직 const isContractTypeActive = inquiryType && ["국내계약", "해외계약", "해외자문"].includes(inquiryType) const isDomesticContractFieldsActive = inquiryType === "국내계약" const isFactualRelationActive = inquiryType && ["국내자문", "해외자문"].includes(inquiryType) const isOverseasFieldsActive = inquiryType && ["해외계약", "해외자문"].includes(inquiryType) // 제목 "기타" 선택 여부 확인 // const isTitleOther = titleValue === "기타" // 검토부문 변경시 관련 필드 초기화 React.useEffect(() => { if (reviewDepartment === "준법문의") { setIsCustomTitle(false) form.setValue("inquiryType", undefined) // 제목 초기화 (기타 상태였거나 값이 없으면 기본값으로) const currentTitle = form.getValues("title") if (!currentTitle || currentTitle === "GTC검토") { form.setValue("title", "CP검토") } // 법무검토 전용 필드들 초기화 form.setValue("contractProjectName", "") form.setValue("contractType", "") form.setValue("contractCounterparty", "") form.setValue("counterpartyType", undefined) form.setValue("contractPeriod", "") form.setValue("contractAmount", "") form.setValue("factualRelation", "") form.setValue("projectNumber", "") form.setValue("shipownerOrderer", "") form.setValue("projectType", "") form.setValue("governingLaw", "") } else { setIsCustomTitle(false) // 제목 초기화 (기타 상태였거나 값이 없으면 기본값으로) const currentTitle = form.getValues("title") if (!currentTitle || currentTitle === "CP검토") { form.setValue("title", "GTC검토") } form.setValue("isPublic", false) } }, [reviewDepartment, form]) // 문의종류 변경시 관련 필드 초기화 React.useEffect(() => { if (inquiryType) { // 계약서 종류 초기화 (옵션이 달라지므로) form.setValue("contractType", "") // 조건에 맞지 않는 필드들 초기화 if (!isDomesticContractFieldsActive) { form.setValue("contractCounterparty", "") form.setValue("counterpartyType", undefined) form.setValue("contractPeriod", "") form.setValue("contractAmount", "") } if (!isFactualRelationActive) { form.setValue("factualRelation", "") } if (!isOverseasFieldsActive) { form.setValue("projectNumber", "") form.setValue("shipownerOrderer", "") form.setValue("projectType", "") form.setValue("governingLaw", "") } } }, [inquiryType, isDomesticContractFieldsActive, isFactualRelationActive, isOverseasFieldsActive, form]) // 에디터 내용이 변경될 때 폼에 반영 React.useEffect(() => { form.setValue("requestContent", editorContent) }, [editorContent, form]) // 첨부파일 처리 const handleFileChange = (event: React.ChangeEvent) => { const files = Array.from(event.target.files || []) setAttachments(prev => [...prev, ...files]) } const removeAttachment = (index: number) => { setAttachments(prev => prev.filter((_, i) => i !== index)) } // 폼 제출 async function onSubmit(data: RequestReviewFormValues) { if (!work) return console.log("Request review data:", data) console.log("Work to review:", work) console.log("Attachments:", attachments) setIsSubmitting(true) try { const result = await requestReview(work.id, data, attachments) if (result.success) { toast.success(result.data?.message || `법무업무 #${work.id}에 대한 검토요청이 완료되었습니다.`) onOpenChange(false) handleReset() onSuccess?.() } else { toast.error(result.error || "검토요청 중 오류가 발생했습니다.") } } catch (error) { console.error("Error requesting review:", error) toast.error("검토요청 중 오류가 발생했습니다.") } finally { setIsSubmitting(false) } } // 폼 리셋 함수 const handleReset = () => { const defaultDepartment = getDefaultReviewDepartment() setIsCustomTitle(false) // 추가 form.reset({ dueDate: "", assignee: "", notificationMethod: "both", reviewDepartment: defaultDepartment, title: defaultDepartment === "준법문의" ? "CP검토" : "GTC검토", requestContent: "", isPublic: false, }) setAttachments([]) setEditorContent("") } // 다이얼로그 닫기 핸들러 const handleOpenChange = (open: boolean) => { onOpenChange(open) if (!open) { handleReset() } } // 제목 옵션 (검토부문에 따라 다름) const getTitleOptions = () => { if (reviewDepartment === "준법문의") { return [ { value: "CP검토", label: "CP검토" }, { value: "기타", label: "기타 (직접입력)" } ] } else { return [ { value: "GTC검토", label: "GTC검토" }, { value: "기타", label: "기타 (직접입력)" } ] } } // 계약서 종류 옵션 (문의종류에 따라 다름) const getContractTypeOptions = () => { if (inquiryType === "국내계약") { return [ { value: "공사도급계약", label: "공사도급계약" }, { value: "제작납품계약", label: "제작납품계약" }, { value: "자재매매계약", label: "자재매매계약" }, { value: "용역위탁계약", label: "용역위탁계약" }, { value: "기술사용 및 개발계약", label: "기술사용 및 개발계약" }, { value: "운송 및 자재관리 계약", label: "운송 및 자재관리 계약" }, { value: "자문 등 위임계약", label: "자문 등 위임계약" }, { value: "양해각서", label: "양해각서" }, { value: "양수도 계약", label: "양수도 계약" }, { value: "합의서", label: "합의서" }, { value: "공동도급(운영)협약서", label: "공동도급(운영)협약서" }, { value: "협정서", label: "협정서" }, { value: "약정서", label: "약정서" }, { value: "협의서", label: "협의서" }, { value: "기타", label: "기타" }, { value: "비밀유지계약서", label: "비밀유지계약서" }, { value: "분양계약서", label: "분양계약서" }, ] } else { // 해외계약/해외자문 return [ { value: "Shipbuilding Contract", label: "Shipbuilding Contract" }, { value: "Offshore Contract (EPCI, FEED)", label: "Offshore Contract (EPCI, FEED)" }, { value: "Supplementary / Addendum", label: "Supplementary / Addendum" }, { value: "Subcontract / GTC / PTC / PO", label: "Subcontract / GTC / PTC / PO" }, { value: "Novation / Assignment", label: "Novation / Assignment" }, { value: "NDA (Confidential, Secrecy)", label: "NDA (Confidential, Secrecy)" }, { value: "Warranty", label: "Warranty" }, { value: "Waiver and Release", label: "Waiver and Release" }, { value: "Bond (PG, RG, Advanced Payment)", label: "Bond (PG, RG, Advanced Payment)" }, { value: "MOU / LOI / LOA", label: "MOU / LOI / LOA" }, { value: "Power of Attorney (POA)", label: "Power of Attorney (POA)" }, { value: "Commission Agreement", label: "Commission Agreement" }, { value: "Consortium Agreement", label: "Consortium Agreement" }, { value: "JV / JDP Agreement", label: "JV / JDP Agreement" }, { value: "Engineering Service Contract", label: "Engineering Service Contract" }, { value: "Consultancy Service Agreement", label: "Consultancy Service Agreement" }, { value: "Purchase / Lease Agreement", label: "Purchase / Lease Agreement" }, { value: "Financial / Loan / Covenant", label: "Financial / Loan / Covenant" }, { value: "Other Contract / Agreement", label: "Other Contract / Agreement" }, ] } } // 프로젝트 종류 옵션 const getProjectTypeOptions = () => { return [ { value: "BARGE VESSEL", label: "BARGE VESSEL" }, { value: "BULK CARRIER", label: "BULK CARRIER" }, { value: "CHEMICAL CARRIER", label: "CHEMICAL CARRIER" }, { value: "FULL CONTAINER", label: "FULL CONTAINER" }, { value: "CRUDE OIL TANKER", label: "CRUDE OIL TANKER" }, { value: "CRUISE SHIP", label: "CRUISE SHIP" }, { value: "DRILL SHIP", label: "DRILL SHIP" }, { value: "FIELD DEVELOPMENT SHIP", label: "FIELD DEVELOPMENT SHIP" }, { value: "FLOATING PRODUCTION STORAGE OFFLOADING", label: "FLOATING PRODUCTION STORAGE OFFLOADING" }, { value: "CAR-FERRY & PASSENGER VESSEL", label: "CAR-FERRY & PASSENGER VESSEL" }, { value: "FLOATING STORAGE OFFLOADING", label: "FLOATING STORAGE OFFLOADING" }, { value: "HEAVY DECK CARGO", label: "HEAVY DECK CARGO" }, { value: "PRODUCT OIL TANKER", label: "PRODUCT OIL TANKER" }, { value: "HIGH SPEED LINER", label: "HIGH SPEED LINER" }, { value: "JACK-UP", label: "JACK-UP" }, { value: "LIQUEFIED NATURAL GAS CARRIER", label: "LIQUEFIED NATURAL GAS CARRIER" }, { value: "LIQUEFIED PETROLEUM GAS CARRIER", label: "LIQUEFIED PETROLEUM GAS CARRIER" }, { value: "MULTIPURPOSE CARGO CARRIER", label: "MULTIPURPOSE CARGO CARRIER" }, { value: "ORE-BULK-OIL CARRIER", label: "ORE-BULK-OIL CARRIER" }, { value: "OIL TANKER", label: "OIL TANKER" }, { value: "OTHER VESSEL", label: "OTHER VESSEL" }, { value: "PURE CAR CARRIER", label: "PURE CAR CARRIER" }, { value: "PRODUCT CARRIER", label: "PRODUCT CARRIER" }, { value: "PLATFORM", label: "PLATFORM" }, { value: "PUSHER", label: "PUSHER" }, { value: "REEFER TRANSPORT VESSEL", label: "REEFER TRANSPORT VESSEL" }, { value: "ROLL-ON ROLL-OFF VESSEL", label: "ROLL-ON ROLL-OFF VESSEL" }, { value: "SEMI RIG", label: "SEMI RIG" }, { value: "SUPPLY ANCHOR HANDLING VESSEL", label: "SUPPLY ANCHOR HANDLING VESSEL" }, { value: "SHUTTLE TANKER", label: "SHUTTLE TANKER" }, { value: "SUPPLY VESSEL", label: "SUPPLY VESSEL" }, { value: "TOPSIDE", label: "TOPSIDE" }, { value: "TUG SUPPLY VESSEL", label: "TUG SUPPLY VESSEL" }, { value: "VERY LARGE CRUDE OIL CARRIER", label: "VERY LARGE CRUDE OIL CARRIER" }, { value: "WELL INTERVENTION SHIP", label: "WELL INTERVENTION SHIP" }, { value: "WIND TURBINE INSTALLATION VESSEL", label: "WIND TURBINE INSTALLATION VESSEL" }, { value: "기타", label: "기타" }, ] } if (!work) { return null } // 검토요청 불가능한 경우 안내 메시지 if (!canRequest) { return ( 검토요청 불가 {requestCheckMessage}
) } return ( {/* 고정 헤더 */}
검토요청 발송 법무업무 #{work.id}에 대한 상세한 검토를 요청합니다.
{/* 스크롤 가능한 콘텐츠 영역 */}
{/* 선택된 업무 정보 */} 검토 대상 업무
업무 ID: #{work.id}
구분: {work.category} {work.isUrgent && ( 긴급 )}
벤더: {work.vendorCode} - {work.vendorName}
요청자: {work.reviewer || "미지정"}
답변요청일: {work.requestDate || "미설정"}
상태: {work.status}
{/* 기본 설정 */} 기본 설정 {/* 검토 완료 희망일 */} ( 검토 완료 희망일 )} /> {/* 법무업무 상세 정보 */} 법무업무 상세 정보 {/* 검토부문 */} ( 검토부문 )} /> {/* 문의종류 (법무검토 선택시만) */} {reviewDepartment === "법무검토" && ( ( 문의종류 )} /> )} {/* 제목 - 조건부 렌더링 */} ( 제목 {!isCustomTitle ? ( // Select 모드 ) : ( // Input 모드 (기타 선택시)
기타
field.onChange(e.target.value)} autoFocus />
)}
)} /> {/* 준법문의 전용 필드들 */} {reviewDepartment === "준법문의" && ( (
공개여부
준법문의 공개 설정
)} /> )} {/* 법무검토 전용 필드들 */} {reviewDepartment === "법무검토" && (
{/* 계약명/프로젝트명 */} ( 계약명/프로젝트명 )} /> {/* 계약서 종류 - 조건부 활성화 */} {isContractTypeActive && ( ( 계약서 종류 )} /> )} {/* 국내계약 전용 필드들 */} {isDomesticContractFieldsActive && (
{/* 계약상대방 */} ( 계약상대방 )} /> {/* 계약상대방 구분 */} ( 계약상대방 구분 )} /> {/* 계약기간 */} ( 계약기간 )} /> {/* 계약금액 */} ( 계약금액 )} />
)} {/* 사실관계 - 조건부 활성화 */} {isFactualRelationActive && ( ( 사실관계