From 08b73d56c2d887931cecdf2b0af6b277381763e6 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 6 Nov 2025 17:44:59 +0900
Subject: (김준회) 결재 프리뷰 공통컴포넌트 작성 및 index.ts --> client.ts 분리
(서버사이드 코드가 번들링되어 클라측에서 실행되는 문제 해결 목적)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../editor/approval-template-editor.tsx | 358 +++++++--------------
1 file changed, 121 insertions(+), 237 deletions(-)
(limited to 'lib/approval-template')
diff --git a/lib/approval-template/editor/approval-template-editor.tsx b/lib/approval-template/editor/approval-template-editor.tsx
index 242504e7..7fe7c700 100644
--- a/lib/approval-template/editor/approval-template-editor.tsx
+++ b/lib/approval-template/editor/approval-template-editor.tsx
@@ -2,16 +2,13 @@
import * as React from "react"
import { toast } from "sonner"
-import { Loader, Save, ArrowLeft, Code2, Eye } from "lucide-react"
+import { Loader, Save, ArrowLeft, Eye } from "lucide-react"
import Link from "next/link"
-import dynamic from "next/dynamic"
-import type { Editor as ToastEditor } from "@toast-ui/editor"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Separator } from "@/components/ui/separator"
import { Badge } from "@/components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
@@ -24,51 +21,20 @@ import { getActiveApprovalTemplateCategories, type ApprovalTemplateCategory } fr
import { useSession } from "next-auth/react"
import { useRouter, usePathname } from "next/navigation"
-// Toast UI Editor를 dynamic import로 불러옴 (SSR 방지)
-const Editor = dynamic(
- async () => {
- // CSS를 먼저 로드
- // @ts-expect-error - CSS 파일은 타입 선언이 없지만 런타임에 정상 작동
- await import("@toast-ui/editor/dist/toastui-editor.css")
- const mod = await import("@toast-ui/react-editor")
- return mod.Editor
- },
- {
- ssr: false,
- loading: () =>
에디터 로드 중...
,
- }
-)
-
interface ApprovalTemplateEditorProps {
templateId: string
initialTemplate: ApprovalTemplate
- staticVariables?: Array<{ variableName: string }>
approvalLineOptions: Array<{ id: string; name: string }>
}
-export function ApprovalTemplateEditor({ templateId, initialTemplate, staticVariables = [], approvalLineOptions }: ApprovalTemplateEditorProps) {
- const { data: session } = useSession()
- const router = useRouter()
- const pathname = usePathname()
- const editorRef = React.useRef(null)
+export function ApprovalTemplateEditor({ templateId, initialTemplate, approvalLineOptions }: ApprovalTemplateEditorProps) {
+ const { data: session } = useSession()
+ const router = useRouter()
+ const pathname = usePathname()
const [template, setTemplate] = React.useState(initialTemplate)
const [isSaving, startSaving] = React.useTransition()
- const [previewContent, setPreviewContent] = React.useState(initialTemplate.content)
- const [editMode, setEditMode] = React.useState<"wysiwyg" | "html">("wysiwyg")
- const [htmlSource, setHtmlSource] = React.useState(initialTemplate.content)
- const [editorKey, setEditorKey] = React.useState(0) // 에디터 재마운트를 위한 키
-
- // 편집기에 전달할 변수 목록
- // 템플릿(DB) 변수 + 정적(config) 변수 병합
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const dbVariables: string[] = Array.isArray((template as any).variables)
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (template as any).variables.map((v: any) => v.variableName)
- : []
-
- const mergedVariables = Array.from(new Set([...dbVariables, ...staticVariables.map((v) => v.variableName)]))
-
- const variableOptions = mergedVariables.map((name) => ({ label: name, html: `{{${name}}}` }))
+ const [htmlContent, setHtmlContent] = React.useState(initialTemplate.content)
+ const [previewKey, setPreviewKey] = React.useState(0) // 미리보기 업데이트용
const [form, setForm] = React.useState({
name: template.name,
@@ -140,18 +106,10 @@ export function ApprovalTemplateEditor({ templateId, initialTemplate, staticVari
setForm((prev) => ({ ...prev, [name]: value }))
}
- // 편집 모드 토글
- function toggleEditMode() {
- if (editMode === "wysiwyg") {
- // WYSIWYG → HTML: 에디터에서 HTML 가져오기
- const currentHtml = editorRef.current?.getHTML() || ""
- setHtmlSource(currentHtml)
- setEditMode("html")
- } else {
- // HTML → WYSIWYG: 에디터를 재마운트하여 수정된 HTML 반영
- setEditorKey(prev => prev + 1)
- setEditMode("wysiwyg")
- }
+ // 미리보기 새로고침
+ function refreshPreview() {
+ setPreviewKey((prev) => prev + 1)
+ toast.success("미리보기를 업데이트했습니다")
}
function handleSave() {
@@ -161,15 +119,10 @@ export function ApprovalTemplateEditor({ templateId, initialTemplate, staticVari
return
}
- // 현재 모드에 따라 HTML 가져오기
- const content = editMode === "wysiwyg"
- ? editorRef.current?.getHTML() || ""
- : htmlSource
-
const { success, error, data } = await updateApprovalTemplateAction(templateId, {
name: form.name,
subject: form.subject,
- content,
+ content: htmlContent,
description: form.description,
category: form.category || undefined,
approvalLineId: form.approvalLineId ? form.approvalLineId : null,
@@ -215,6 +168,9 @@ export function ApprovalTemplateEditor({ templateId, initialTemplate, staticVari
{template.description || "결재 템플릿 편집"}
+