From 18954df6565108a469fb1608ea3715dd9bb1b02d Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Mon, 1 Sep 2025 09:12:09 +0000
Subject: (대표님) 구매 기본계약, gtc 개발
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../gtc-vendor/update-gtc-clause-sheet.tsx         | 522 +++++++++++++++++++++
 1 file changed, 522 insertions(+)
 create mode 100644 lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx

(limited to 'lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx')

diff --git a/lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx b/lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx
new file mode 100644
index 00000000..3487ebbf
--- /dev/null
+++ b/lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx
@@ -0,0 +1,522 @@
+// 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<typeof Sheet> {
+  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<ClauseImage[]>([])
+
+  console.log(vendorInfo,"vendorInfo")
+  
+  const currentUserId = React.useMemo(() => {
+    return session?.user?.id ? Number(session.user.id) : null
+  }, [session])
+  
+  const form = useForm<UpdateVendorGtcClauseSchema>({
+    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 (
+    <Sheet {...props}>
+      <SheetContent className="flex flex-col sm:max-w-2xl h-full">
+        <SheetHeader className="text-left flex-shrink-0">
+          <SheetTitle>벤더 GTC 조항 협의</SheetTitle>
+          <SheetDescription>
+            {vendorName ? `${vendorName}과(와)의 조항 협의 내용을 입력하세요` : '벤더별 조항 수정사항을 입력하세요'}
+          </SheetDescription>
+        </SheetHeader>
+
+        <div className="flex-1 overflow-y-auto">
+          {/* 기존 조항 정보 (읽기 전용) */}
+          <div className="space-y-4 p-4 bg-muted/30 rounded-lg mb-4">
+            <div className="flex items-center justify-between">
+              <h3 className="text-sm font-semibold">표준 조항 내용</h3>
+              <Badge variant="outline">
+                {getDepthBadge(gtcClause?.depth || 0)}
+              </Badge>
+            </div>
+            
+            <div className="space-y-3">
+              <div>
+                <Label className="text-xs text-muted-foreground">채번</Label>
+                <div className="text-sm font-mono mt-1">{gtcClause?.itemNumber}</div>
+              </div>
+              
+              {gtcClause?.category && (
+                <div>
+                  <Label className="text-xs text-muted-foreground">분류</Label>
+                  <div className="text-sm mt-1">{gtcClause.category}</div>
+                </div>
+              )}
+              
+              <div>
+                <Label className="text-xs text-muted-foreground">소제목</Label>
+                <div className="text-sm font-medium mt-1">{gtcClause?.subtitle}</div>
+              </div>
+              
+              {gtcClause?.content && (
+                <div>
+                  <Label className="text-xs text-muted-foreground">상세항목</Label>
+                  <div className="text-sm mt-1 p-2 bg-background rounded border">
+                    <pre className="whitespace-pre-wrap font-sans">{gtcClause.content}</pre>
+                  </div>
+                </div>
+              )}
+            </div>
+          </div>
+
+          <Separator className="my-4" />
+
+          {/* 벤더별 수정 폼 */}
+          <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 p-4">
+              <div className="flex items-center gap-2 mb-4">
+                <AlertCircle className="h-4 w-4 text-blue-500" />
+                <p className="text-sm text-muted-foreground">
+                  수정할 항목에 체크하고 내용을 입력하세요. 체크하지 않은 항목은 표준 조항의 내용을 그대로 사용합니다.
+                </p>
+              </div>
+
+              {/* 협의 상태 */}
+              <FormField
+                control={form.control}
+                name="reviewStatus"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>협의 상태</FormLabel>
+                    <Select onValueChange={field.onChange} defaultValue={field.value}>
+                      <FormControl>
+                        <SelectTrigger>
+                          <SelectValue placeholder="협의 상태를 선택하세요" />
+                        </SelectTrigger>
+                      </FormControl>
+                      <SelectContent>
+                        <SelectItem value="draft">초안</SelectItem>
+                        <SelectItem value="pending">협의 대기</SelectItem>
+                        <SelectItem value="reviewing">협의 중</SelectItem>
+                        <SelectItem value="approved">승인됨</SelectItem>
+                        <SelectItem value="rejected">거부됨</SelectItem>
+                        <SelectItem value="revised">수정됨</SelectItem>
+                      </SelectContent>
+                    </Select>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              {/* 제외 여부 */}
+              <FormField
+                control={form.control}
+                name="isExcluded"
+                render={({ field }) => (
+                  <FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
+                    <FormControl>
+                      <Checkbox
+                        checked={field.value}
+                        onCheckedChange={field.onChange}
+                      />
+                    </FormControl>
+                    <div className="space-y-1 leading-none">
+                      <FormLabel>
+                        이 조항을 벤더 계약에서 제외
+                      </FormLabel>
+                      <FormDescription>
+                        체크하면 최종 계약서에서 이 조항이 제외됩니다
+                      </FormDescription>
+                    </div>
+                  </FormItem>
+                )}
+              />
+
+              {/* 채번 수정 */}
+              <div className="space-y-2 border rounded-lg p-3">
+                <FormField
+                  control={form.control}
+                  name="isNumberModified"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center space-x-3 space-y-0">
+                      <FormControl>
+                        <Checkbox
+                          checked={field.value}
+                          onCheckedChange={field.onChange}
+                        />
+                      </FormControl>
+                      <FormLabel className="font-normal cursor-pointer">
+                        채번 수정
+                      </FormLabel>
+                    </FormItem>
+                  )}
+                />
+                
+                {form.watch("isNumberModified") && (
+                  <FormField
+                    control={form.control}
+                    name="modifiedItemNumber"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormControl>
+                          <Input 
+                            placeholder="수정할 채번 입력"
+                            {...field} 
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                )}
+              </div>
+
+              {/* 분류 수정 */}
+              <div className="space-y-2 border rounded-lg p-3">
+                <FormField
+                  control={form.control}
+                  name="isCategoryModified"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center space-x-3 space-y-0">
+                      <FormControl>
+                        <Checkbox
+                          checked={field.value}
+                          onCheckedChange={field.onChange}
+                        />
+                      </FormControl>
+                      <FormLabel className="font-normal cursor-pointer">
+                        분류 수정
+                      </FormLabel>
+                    </FormItem>
+                  )}
+                />
+                
+                {form.watch("isCategoryModified") && (
+                  <FormField
+                    control={form.control}
+                    name="modifiedCategory"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormControl>
+                          <Input 
+                            placeholder="수정할 분류 입력"
+                            {...field} 
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                )}
+              </div>
+
+              {/* 소제목 수정 */}
+              <div className="space-y-2 border rounded-lg p-3">
+                <FormField
+                  control={form.control}
+                  name="isSubtitleModified"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center space-x-3 space-y-0">
+                      <FormControl>
+                        <Checkbox
+                          checked={field.value}
+                          onCheckedChange={field.onChange}
+                        />
+                      </FormControl>
+                      <FormLabel className="font-normal cursor-pointer">
+                        소제목 수정
+                      </FormLabel>
+                    </FormItem>
+                  )}
+                />
+                
+                {form.watch("isSubtitleModified") && (
+                  <FormField
+                    control={form.control}
+                    name="modifiedSubtitle"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormControl>
+                          <Input 
+                            placeholder="수정할 소제목 입력"
+                            {...field} 
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                )}
+              </div>
+
+              {/* 내용 수정 */}
+              <div className="space-y-2 border rounded-lg p-3">
+                <FormField
+                  control={form.control}
+                  name="isContentModified"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center space-x-3 space-y-0">
+                      <FormControl>
+                        <Checkbox
+                          checked={field.value}
+                          onCheckedChange={field.onChange}
+                        />
+                      </FormControl>
+                      <FormLabel className="font-normal cursor-pointer">
+                        상세항목 수정
+                      </FormLabel>
+                    </FormItem>
+                  )}
+                />
+                
+                {form.watch("isContentModified") && (
+                  <FormField
+                    control={form.control}
+                    name="modifiedContent"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormControl>
+                          <MarkdownImageEditor
+                            content={field.value || ""}
+                            images={images}
+                            onChange={handleContentImageChange}
+                            placeholder="수정할 내용을 입력하세요..."
+                            rows={6}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                )}
+              </div>
+
+              {/* 협의 이력 표시 섹션 */}
+{vendorInfo?.negotiationHistory && vendorInfo.negotiationHistory.length > 0 && (
+  <div className="space-y-3 mb-4">
+    <h3 className="text-sm font-semibold">협의 이력</h3>
+    <div className="border rounded-lg p-3 max-h-64 overflow-y-auto space-y-3">
+      {vendorInfo.negotiationHistory.map((history, index) => (
+        <div key={index} className="border-b pb-3 last:border-b-0 last:pb-0">
+          <div className="flex justify-between text-xs text-muted-foreground mb-1">
+            <div className="font-medium">
+              {history.actorName || "시스템"}
+              {history.previousStatus && history.newStatus && (
+                <span className="ml-2 text-xs">
+                  ({history.previousStatus} → {history.newStatus})
+                </span>
+              )}
+            </div>
+            <div>
+              {new Date(history.createdAt).toLocaleString('ko-KR', {
+                year: 'numeric',
+                month: '2-digit',
+                day: '2-digit',
+                hour: '2-digit',
+                minute: '2-digit'
+              })}
+            </div>
+          </div>
+          {history.comment && (
+            <div className="text-sm p-2 bg-muted/30 rounded">
+              {history.comment}
+            </div>
+          )}
+        </div>
+      ))}
+    </div>
+  </div>
+)}
+
+              {/* 협의 노트 */}
+              <FormField
+                control={form.control}
+                name="negotiationNote"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>협의 메모</FormLabel>
+                    <FormControl>
+                      <Textarea
+                        placeholder="협의 과정에서의 메모나 특이사항을 기록하세요..."
+                        {...field}
+                        rows={3}
+                      />
+                    </FormControl>
+                    <FormDescription>
+                      벤더와의 협의 내용이나 변경 사유를 기록합니다
+                    </FormDescription>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+            </form>
+          </Form>
+        </div>
+
+        <SheetFooter className="gap-2 pt-2 sm:space-x-0 flex-shrink-0">
+          <SheetClose asChild>
+            <Button type="button" variant="outline">
+              취소
+            </Button>
+          </SheetClose>
+
+          <Button 
+            onClick={form.handleSubmit(onSubmit)} 
+            disabled={isUpdatePending}
+          >
+            {isUpdatePending && (
+              <Loader
+                className="mr-2 size-4 animate-spin"
+                aria-hidden="true"
+              />
+            )}
+            저장
+          </Button>
+        </SheetFooter>
+      </SheetContent>
+    </Sheet>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

