From c228a89c2834ee63b209bad608837c39643f350e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 28 Jul 2025 11:44:16 +0000 Subject: (대표님) 의존성 docx 추가, basicContract API, gtc(계약일반조건), 벤더평가 esg 평가데이터 내보내기 개선, S-EDP 피드백 대응(CLS_ID, ITEM NO 등) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gtc-clauses/table/markdown-image-editor.tsx | 360 +++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 lib/gtc-contract/gtc-clauses/table/markdown-image-editor.tsx (limited to 'lib/gtc-contract/gtc-clauses/table/markdown-image-editor.tsx') diff --git a/lib/gtc-contract/gtc-clauses/table/markdown-image-editor.tsx b/lib/gtc-contract/gtc-clauses/table/markdown-image-editor.tsx new file mode 100644 index 00000000..422d8475 --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/table/markdown-image-editor.tsx @@ -0,0 +1,360 @@ +"use client" + +import * as React from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Upload, X, Image as ImageIcon, Eye, EyeOff } from "lucide-react" +import { toast } from "sonner" +import { cn } from "@/lib/utils" + +interface ClauseImage { + id: string + url: string + fileName: string + size: number +} + +interface MarkdownImageEditorProps { + content: string + images: ClauseImage[] + onChange: (content: string, images: ClauseImage[]) => void + placeholder?: string + rows?: number + className?: string +} + +export function MarkdownImageEditor({ + content, + images, + onChange, + placeholder = "텍스트를 입력하고, 이미지를 삽입하려면 '이미지 추가' 버튼을 클릭하세요.", + rows = 6, + className +}: MarkdownImageEditorProps) { + const [imageUploadOpen, setImageUploadOpen] = React.useState(false) + const [showPreview, setShowPreview] = React.useState(false) + const [uploading, setUploading] = React.useState(false) + const textareaRef = React.useRef(null) + + // 이미지 업로드 핸들러 + const handleImageUpload = async (file: File) => { + if (!file.type.startsWith('image/')) { + toast.error('이미지 파일만 업로드 가능합니다.') + return + } + + if (file.size > 5 * 1024 * 1024) { // 5MB 제한 + toast.error('파일 크기는 5MB 이하여야 합니다.') + return + } + + setUploading(true) + try { + // 실제 구현에서는 서버로 업로드 + const uploadedUrl = await uploadImage(file) + const imageId = `image${Date.now()}` + + // 이미지 배열에 추가 + const newImages = [...images, { + id: imageId, + url: uploadedUrl, + fileName: file.name, + size: file.size, + }] + + // 커서 위치에 이미지 참조 삽입 + const imageRef = `![${imageId}]` + const textarea = textareaRef.current + + if (textarea) { + const start = textarea.selectionStart + const end = textarea.selectionEnd + const newContent = content.substring(0, start) + imageRef + content.substring(end) + + onChange(newContent, newImages) + + // 커서 위치를 이미지 참조 뒤로 이동 + setTimeout(() => { + textarea.focus() + textarea.setSelectionRange(start + imageRef.length, start + imageRef.length) + }, 0) + } else { + // 텍스트 끝에 추가 + const newContent = content + (content ? '\n\n' : '') + imageRef + onChange(newContent, newImages) + } + + toast.success('이미지가 추가되었습니다.') + setImageUploadOpen(false) + } catch (error) { + toast.error('이미지 업로드에 실패했습니다.') + console.error('Image upload error:', error) + } finally { + setUploading(false) + } + } + + // 이미지 제거 + const removeImage = (imageId: string) => { + // 이미지 배열에서 제거 + const newImages = images.filter(img => img.id !== imageId) + + // 텍스트에서 이미지 참조 제거 + const imageRef = `![${imageId}]` + const newContent = content.replace(new RegExp(`\\!\\[${imageId}\\]`, 'g'), '') + + onChange(newContent, newImages) + toast.success('이미지가 제거되었습니다.') + } + + // 커서 위치에 텍스트 삽입 + const insertAtCursor = (text: string) => { + const textarea = textareaRef.current + if (!textarea) return + + const start = textarea.selectionStart + const end = textarea.selectionEnd + const newContent = content.substring(0, start) + text + content.substring(end) + + onChange(newContent, images) + + // 커서 위치 조정 + setTimeout(() => { + textarea.focus() + textarea.setSelectionRange(start + text.length, start + text.length) + }, 0) + } + + return ( +
+ {/* 에디터 툴바 */} +
+
+ + + +
+ + {images.length > 0 && ( + + {images.length}개 이미지 첨부됨 + + )} +
+ + {/* 에디터 영역 */} + {showPreview ? ( + /* 미리보기 모드 */ +
+
+ {renderMarkdownPreview(content, images)} +
+
+ ) : ( + /* 편집 모드 */ +
+