// update-vendor-gtc-clause-sheet.tsx "use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Loader, Info, AlertCircle } from "lucide-react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" 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 { Label } from "@/components/ui/label" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Separator } from "@/components/ui/separator" import { type GtcClauseTreeView } from "@/db/schema/gtc" import { updateVendorGtcClauseSchema, type UpdateVendorGtcClauseSchema } from "@/lib/gtc-contract/gtc-clauses/validations" import { updateVendorGtcClause } from "@/lib/gtc-contract/gtc-clauses/service" import { useSession } from "next-auth/react" import { MarkdownImageEditor } from "./markdown-image-editor" import { Checkbox } from "@/components/ui/checkbox" interface ClauseImage { id: string url: string fileName: string size: number savedName?: string mimeType?: string width?: number height?: number hash?: string } export interface UpdateVendorGtcClauseSheetProps extends React.ComponentPropsWithRef { gtcClause: GtcClauseTreeView | null vendorInfo?: any // 벤더 조항 정보 documentId: number vendorId: number vendorName?: string } export function UpdateGtcClauseSheet ({ gtcClause, vendorInfo, documentId, vendorId, vendorName, ...props }: UpdateVendorGtcClauseSheetProps) { const [isUpdatePending, startUpdateTransition] = React.useTransition() const { data: session } = useSession() const [images, setImages] = React.useState([]) console.log(vendorInfo,"vendorInfo") const currentUserId = React.useMemo(() => { return session?.user?.id ? Number(session.user.id) : null }, [session]) const form = useForm({ resolver: zodResolver(updateVendorGtcClauseSchema), defaultValues: { modifiedItemNumber: "", modifiedCategory: "", modifiedSubtitle: "", modifiedContent: "", isNumberModified: false, isCategoryModified: false, isSubtitleModified: false, isContentModified: false, reviewStatus: "draft", negotiationNote: "", isExcluded: false, }, }) // 벤더 정보가 있으면 초기값 세팅 React.useEffect(() => { if (vendorInfo) { form.reset({ modifiedItemNumber: vendorInfo.modifiedItemNumber || "", modifiedCategory: vendorInfo.modifiedCategory || "", modifiedSubtitle: vendorInfo.modifiedSubtitle || "", modifiedContent: vendorInfo.modifiedContent || "", isNumberModified: vendorInfo.isNumberModified || false, isCategoryModified: vendorInfo.isCategoryModified || false, isSubtitleModified: vendorInfo.isSubtitleModified || false, isContentModified: vendorInfo.isContentModified || false, reviewStatus: vendorInfo.reviewStatus || "draft", negotiationNote: vendorInfo.negotiationNote || "", isExcluded: vendorInfo.isExcluded || false, }) setImages((vendorInfo.images as any[]) || []) } }, [vendorInfo, form]) async function onSubmit(input: UpdateVendorGtcClauseSchema) { startUpdateTransition(async () => { if (!gtcClause || !currentUserId) { toast.error("조항 정보를 찾을 수 없습니다.") return } try { const result = await updateVendorGtcClause({ baseClauseId: gtcClause.id, documentId, vendorId, ...input, images, updatedById: currentUserId, }) if (result.error) { toast.error(result.error) return } form.reset() props.onOpenChange?.(false) toast.success("벤더 협의 내용이 저장되었습니다!") } catch (error) { toast.error("벤더 협의 저장 중 오류가 발생했습니다.") } }) } const getDepthBadge = (depth: number) => { const levels = ["1단계", "2단계", "3단계", "4단계", "5단계+"] return levels[depth] || levels[4] } const handleContentImageChange = (content: string, newImages: ClauseImage[]) => { form.setValue("modifiedContent", content) setImages(newImages) } return ( 벤더 GTC 조항 협의 {vendorName ? `${vendorName}과(와)의 조항 협의 내용을 입력하세요` : '벤더별 조항 수정사항을 입력하세요'}
{/* 기존 조항 정보 (읽기 전용) */}

표준 조항 내용

{getDepthBadge(gtcClause?.depth || 0)}
{gtcClause?.itemNumber}
{gtcClause?.category && (
{gtcClause.category}
)}
{gtcClause?.subtitle}
{gtcClause?.content && (
{gtcClause.content}
)}
{/* 벤더별 수정 폼 */}

수정할 항목에 체크하고 내용을 입력하세요. 체크하지 않은 항목은 표준 조항의 내용을 그대로 사용합니다.

{/* 협의 상태 */} ( 협의 상태 )} /> {/* 제외 여부 */} (
이 조항을 벤더 계약에서 제외 체크하면 최종 계약서에서 이 조항이 제외됩니다
)} /> {/* 채번 수정 */}
( 채번 수정 )} /> {form.watch("isNumberModified") && ( ( )} /> )}
{/* 분류 수정 */}
( 분류 수정 )} /> {form.watch("isCategoryModified") && ( ( )} /> )}
{/* 소제목 수정 */}
( 소제목 수정 )} /> {form.watch("isSubtitleModified") && ( ( )} /> )}
{/* 내용 수정 */}
( 상세항목 수정 )} /> {form.watch("isContentModified") && ( ( )} /> )}
{/* 협의 이력 표시 섹션 */} {vendorInfo?.negotiationHistory && vendorInfo.negotiationHistory.length > 0 && (

협의 이력

{vendorInfo.negotiationHistory.map((history, index) => (
{history.actorName || "시스템"} {history.previousStatus && history.newStatus && ( ({history.previousStatus} → {history.newStatus}) )}
{new Date(history.createdAt).toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
{history.comment && (
{history.comment}
)}
))}
)} {/* 협의 노트 */} ( 협의 메모