diff options
Diffstat (limited to 'lib/legal-review/status/request-review-dialog.tsx')
| -rw-r--r-- | lib/legal-review/status/request-review-dialog.tsx | 976 |
1 files changed, 976 insertions, 0 deletions
diff --git a/lib/legal-review/status/request-review-dialog.tsx b/lib/legal-review/status/request-review-dialog.tsx new file mode 100644 index 00000000..838752c4 --- /dev/null +++ b/lib/legal-review/status/request-review-dialog.tsx @@ -0,0 +1,976 @@ +"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<typeof requestReviewSchema> + +export function RequestReviewDialog({ + open, + onOpenChange, + work, + onSuccess +}: RequestReviewDialogProps) { + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [attachments, setAttachments] = React.useState<File[]>([]) + const [editorContent, setEditorContent] = React.useState("") + const [canRequest, setCanRequest] = React.useState(true) + const [requestCheckMessage, setRequestCheckMessage] = React.useState("") + + // work의 category에 따라 기본 reviewDepartment 결정 + const getDefaultReviewDepartment = () => { + return work?.category === "CP" ? "준법문의" : "법무검토" + } + + const form = useForm<RequestReviewFormValues>({ + 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 === "준법문의") { + form.setValue("inquiryType", undefined) + // 제목 초기화 (기타 상태였거나 값이 없으면 기본값으로) + const currentTitle = form.getValues("title") + if (currentTitle === "기타" || !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 { + // 제목 초기화 (기타 상태였거나 값이 없으면 기본값으로) + const currentTitle = form.getValues("title") + if (currentTitle === "기타" || !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<HTMLInputElement>) => { + 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() + 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 ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-md"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2 text-amber-600"> + <FileText className="h-5 w-5" /> + 검토요청 불가 + </DialogTitle> + <DialogDescription className="pt-4"> + {requestCheckMessage} + </DialogDescription> + </DialogHeader> + <div className="flex justify-end pt-4"> + <Button onClick={() => onOpenChange(false)}>확인</Button> + </div> + </DialogContent> + </Dialog> + ) + } + + return ( + <Dialog open={open} onOpenChange={handleOpenChange}> + <DialogContent className="max-w-4xl h-[90vh] p-0 flex flex-col"> + {/* 고정 헤더 */} + <div className="flex-shrink-0 p-6 border-b"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <Send className="h-5 w-5" /> + 검토요청 발송 + </DialogTitle> + <DialogDescription> + 법무업무 #{work.id}에 대한 상세한 검토를 요청합니다. + </DialogDescription> + </DialogHeader> + </div> + + <Form {...form}> + <form + onSubmit={form.handleSubmit(onSubmit)} + className="flex flex-col flex-1 min-h-0" + > + {/* 스크롤 가능한 콘텐츠 영역 */} + <div className="flex-1 overflow-y-auto p-6"> + <div className="space-y-6"> + {/* 선택된 업무 정보 */} + <Card className="bg-blue-50 border-blue-200"> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <FileText className="h-5 w-5" /> + 검토 대상 업무 + </CardTitle> + </CardHeader> + <CardContent> + <div className="grid grid-cols-2 gap-4 text-sm"> + <div className="space-y-2"> + <div className="flex items-center gap-2"> + <span className="font-medium">업무 ID:</span> + <Badge variant="outline">#{work.id}</Badge> + </div> + <div className="flex items-center gap-2"> + <span className="font-medium">구분:</span> + <Badge variant={work.category === "CP" ? "default" : "secondary"}> + {work.category} + </Badge> + {work.isUrgent && ( + <Badge variant="destructive" className="text-xs"> + 긴급 + </Badge> + )} + </div> + <div className="flex items-center gap-2"> + <Building className="h-4 w-4" /> + <span className="font-medium">벤더:</span> + <span>{work.vendorCode} - {work.vendorName}</span> + </div> + </div> + <div className="space-y-2"> + <div className="flex items-center gap-2"> + <User className="h-4 w-4" /> + <span className="font-medium">요청자:</span> + <span>{work.reviewer || "미지정"}</span> + </div> + <div className="flex items-center gap-2"> + <Calendar className="h-4 w-4" /> + <span className="font-medium">답변요청일:</span> + <span>{work.requestDate || "미설정"}</span> + </div> + <div className="flex items-center gap-2"> + <span className="font-medium">상태:</span> + <Badge variant="outline">{work.status}</Badge> + </div> + </div> + </div> + </CardContent> + </Card> + + {/* 기본 설정 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">기본 설정</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {/* 검토 완료 희망일 */} + <FormField + control={form.control} + name="dueDate" + render={({ field }) => ( + <FormItem> + <FormLabel className="flex items-center gap-2"> + <Clock className="h-4 w-4" /> + 검토 완료 희망일 + </FormLabel> + <FormControl> + <Input type="date" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> + + {/* 법무업무 상세 정보 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">법무업무 상세 정보</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {/* 검토부문 */} + <FormField + control={form.control} + name="reviewDepartment" + render={({ field }) => ( + <FormItem> + <FormLabel>검토부문</FormLabel> + <Select onValueChange={field.onChange} value={field.value}> + <FormControl> + <SelectTrigger> + <SelectValue placeholder="검토부문 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + <SelectItem value="준법문의">준법문의</SelectItem> + <SelectItem value="법무검토">법무검토</SelectItem> + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + + {/* 문의종류 (법무검토 선택시만) */} + {reviewDepartment === "법무검토" && ( + <FormField + control={form.control} + name="inquiryType" + render={({ field }) => ( + <FormItem> + <FormLabel>문의종류</FormLabel> + <Select onValueChange={field.onChange} value={field.value}> + <FormControl> + <SelectTrigger> + <SelectValue placeholder="문의종류 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + <SelectItem value="국내계약">국내계약</SelectItem> + <SelectItem value="국내자문">국내자문</SelectItem> + <SelectItem value="해외계약">해외계약</SelectItem> + <SelectItem value="해외자문">해외자문</SelectItem> + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + )} + + {/* 제목 - 조건부 렌더링 */} + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem> + <FormLabel>제목</FormLabel> + {!isTitleOther ? ( + // Select 모드 + <Select + onValueChange={(value) => { + field.onChange(value) + if (value !== "기타") { + // 기타가 아닌 값으로 변경시 해당 값으로 설정 + form.setValue("title", value) + } + }} + value={field.value} + > + <FormControl> + <SelectTrigger> + <SelectValue placeholder="제목 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + {getTitleOptions().map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + ) : ( + // Input 모드 (기타 선택시) + <div className="space-y-2"> + <div className="flex items-center gap-2"> + <Badge variant="outline" className="text-xs">기타</Badge> + <Button + type="button" + variant="ghost" + size="sm" + onClick={() => { + const defaultTitle = reviewDepartment === "준법문의" ? "CP검토" : "GTC검토" + form.setValue("title", defaultTitle) + }} + className="h-6 text-xs" + > + 선택 모드로 돌아가기 + </Button> + </div> + <FormControl> + <Input + placeholder="제목을 직접 입력하세요" + value={field.value === "기타" ? "" : field.value} + onChange={(e) => field.onChange(e.target.value)} + autoFocus + /> + </FormControl> + </div> + )} + <FormMessage /> + </FormItem> + )} + /> + + {/* 준법문의 전용 필드들 */} + {reviewDepartment === "준법문의" && ( + <FormField + control={form.control} + name="isPublic" + render={({ field }) => ( + <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4"> + <div className="space-y-0.5"> + <FormLabel className="text-base">공개여부</FormLabel> + <div className="text-sm text-muted-foreground"> + 준법문의 공개 설정 + </div> + </div> + <FormControl> + <Switch + checked={field.value} + onCheckedChange={field.onChange} + /> + </FormControl> + </FormItem> + )} + /> + )} + + {/* 법무검토 전용 필드들 */} + {reviewDepartment === "법무검토" && ( + <div className="space-y-4"> + {/* 계약명/프로젝트명 */} + <FormField + control={form.control} + name="contractProjectName" + render={({ field }) => ( + <FormItem> + <FormLabel>계약명/프로젝트명</FormLabel> + <FormControl> + <Input placeholder="계약명 또는 프로젝트명 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 계약서 종류 - 조건부 활성화 */} + {isContractTypeActive && ( + <FormField + control={form.control} + name="contractType" + render={({ field }) => ( + <FormItem> + <FormLabel>계약서 종류</FormLabel> + <Select onValueChange={field.onChange} value={field.value}> + <FormControl> + <SelectTrigger> + <SelectValue placeholder="계약서 종류 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + {getContractTypeOptions().map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + )} + + {/* 국내계약 전용 필드들 */} + {isDomesticContractFieldsActive && ( + <div className="grid grid-cols-2 gap-4"> + {/* 계약상대방 */} + <FormField + control={form.control} + name="contractCounterparty" + render={({ field }) => ( + <FormItem> + <FormLabel>계약상대방</FormLabel> + <FormControl> + <Input placeholder="계약상대방 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 계약상대방 구분 */} + <FormField + control={form.control} + name="counterpartyType" + render={({ field }) => ( + <FormItem> + <FormLabel>계약상대방 구분</FormLabel> + <Select onValueChange={field.onChange} value={field.value}> + <FormControl> + <SelectTrigger> + <SelectValue placeholder="구분 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + <SelectItem value="법인">법인</SelectItem> + <SelectItem value="개인">개인</SelectItem> + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + + {/* 계약기간 */} + <FormField + control={form.control} + name="contractPeriod" + render={({ field }) => ( + <FormItem> + <FormLabel>계약기간</FormLabel> + <FormControl> + <Input placeholder="계약기간 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 계약금액 */} + <FormField + control={form.control} + name="contractAmount" + render={({ field }) => ( + <FormItem> + <FormLabel>계약금액</FormLabel> + <FormControl> + <Input placeholder="계약금액 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + )} + + {/* 사실관계 - 조건부 활성화 */} + {isFactualRelationActive && ( + <FormField + control={form.control} + name="factualRelation" + render={({ field }) => ( + <FormItem> + <FormLabel>사실관계</FormLabel> + <FormControl> + <Textarea + placeholder="사실관계를 상세히 입력해주세요" + className="min-h-[80px]" + {...field} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + )} + + {/* 해외 관련 필드들 - 조건부 활성화 */} + {isOverseasFieldsActive && ( + <div className="grid grid-cols-2 gap-4"> + {/* 프로젝트번호 */} + <FormField + control={form.control} + name="projectNumber" + render={({ field }) => ( + <FormItem> + <FormLabel>프로젝트번호</FormLabel> + <FormControl> + <Input placeholder="프로젝트번호 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 선주/발주처 */} + <FormField + control={form.control} + name="shipownerOrderer" + render={({ field }) => ( + <FormItem> + <FormLabel>선주/발주처</FormLabel> + <FormControl> + <Input placeholder="선주/발주처 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 프로젝트종류 */} + <FormField + control={form.control} + name="projectType" + render={({ field }) => ( + <FormItem> + <FormLabel>프로젝트종류</FormLabel> + <Select onValueChange={field.onChange} value={field.value}> + <FormControl> + <SelectTrigger> + <SelectValue placeholder="프로젝트종류 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + {getProjectTypeOptions().map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + + {/* 준거법 */} + <FormField + control={form.control} + name="governingLaw" + render={({ field }) => ( + <FormItem> + <FormLabel>준거법</FormLabel> + <FormControl> + <Input placeholder="준거법 입력" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + )} + </div> + )} + + {/* 요청내용 - TiptapEditor로 교체 */} + <FormField + control={form.control} + name="requestContent" + render={({ field }) => ( + <FormItem> + <FormLabel>요청내용</FormLabel> + <FormControl> + <div className="min-h-[250px]"> + <TiptapEditor + content={editorContent} + setContent={setEditorContent} + disabled={isSubmitting} + height="250px" + /> + </div> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 첨부파일 */} + <div className="space-y-2"> + <FormLabel>첨부파일</FormLabel> + <div className="border-2 border-dashed border-muted-foreground/25 rounded-lg p-4"> + <input + type="file" + multiple + onChange={handleFileChange} + className="hidden" + id="file-upload" + /> + <label + htmlFor="file-upload" + className="flex flex-col items-center justify-center cursor-pointer" + > + <Upload className="h-8 w-8 text-muted-foreground mb-2" /> + <span className="text-sm text-muted-foreground"> + 파일을 선택하거나 여기로 드래그하세요 + </span> + </label> + </div> + + {/* 선택된 파일 목록 */} + {attachments.length > 0 && ( + <div className="space-y-2"> + {attachments.map((file, index) => ( + <div key={index} className="flex items-center justify-between bg-muted/50 p-2 rounded"> + <span className="text-sm truncate">{file.name}</span> + <Button + type="button" + variant="ghost" + size="sm" + onClick={() => removeAttachment(index)} + > + <X className="h-4 w-4" /> + </Button> + </div> + ))} + </div> + )} + </div> + </CardContent> + </Card> + </div> + </div> + + {/* 고정 버튼 영역 */} + <div className="flex-shrink-0 border-t p-6"> + <div className="flex justify-end gap-3"> + <Button + type="button" + variant="outline" + onClick={() => handleOpenChange(false)} + disabled={isSubmitting} + > + 취소 + </Button> + <Button + type="submit" + disabled={isSubmitting} + className="bg-blue-600 hover:bg-blue-700" + > + {isSubmitting ? ( + <> + <Loader2 className="mr-2 h-4 w-4 animate-spin" /> + 발송 중... + </> + ) : ( + <> + <Send className="mr-2 h-4 w-4" /> + 검토요청 발송 + </> + )} + </Button> + </div> + </div> + </form> + </Form> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
