From 9da494b0e3bbe7b513521d0915510fe9ee376b8b Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Mon, 21 Jul 2025 07:19:52 +0000
Subject: (대표님, 최겸) 작업사항 - 이메일 템플릿, 메일링, 기술영업 요구사항
 반영
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/email-template/table/create-template-sheet.tsx | 381 +++++++++++++++++++++
 1 file changed, 381 insertions(+)
 create mode 100644 lib/email-template/table/create-template-sheet.tsx

(limited to 'lib/email-template/table/create-template-sheet.tsx')

diff --git a/lib/email-template/table/create-template-sheet.tsx b/lib/email-template/table/create-template-sheet.tsx
new file mode 100644
index 00000000..199e20ab
--- /dev/null
+++ b/lib/email-template/table/create-template-sheet.tsx
@@ -0,0 +1,381 @@
+"use client"
+
+import * as React from "react"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Loader } from "lucide-react"
+import { useForm } from "react-hook-form"
+import { toast } from "sonner"
+import { z } from "zod"
+import { useRouter } from "next/navigation"
+import { useSession } from "next-auth/react"
+
+import { Button } from "@/components/ui/button"
+import {
+    Form,
+    FormControl,
+    FormField,
+    FormItem,
+    FormLabel,
+    FormMessage,
+    FormDescription,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import {
+    Select,
+    SelectContent,
+    SelectItem,
+    SelectTrigger,
+    SelectValue,
+} from "@/components/ui/select"
+import {
+    Sheet,
+    SheetClose,
+    SheetContent,
+    SheetDescription,
+    SheetFooter,
+    SheetHeader,
+    SheetTitle,
+} from "@/components/ui/sheet"
+
+import { createTemplateAction } from "../service"
+import { TEMPLATE_CATEGORY_OPTIONS } from "../validations"
+
+// Validation Schema (수정됨)
+const createTemplateSchema = z.object({
+    name: z.string().min(1, "템플릿 이름은 필수입니다").max(100, "템플릿 이름은 100자 이하여야 합니다"),
+    slug: z.string()
+        .min(1, "Slug는 필수입니다")
+        .max(50, "Slug는 50자 이하여야 합니다")
+        .regex(/^[a-z0-9-]+$/, "Slug는 소문자, 숫자, 하이픈만 사용 가능합니다"),
+    description: z.string().max(500, "설명은 500자 이하여야 합니다").optional(),
+    category: z.string().optional(), // 빈 문자열이나 undefined 모두 허용
+})
+
+
+
+type CreateTemplateSchema = z.infer<typeof createTemplateSchema>
+
+interface CreateTemplateSheetProps
+    extends React.ComponentPropsWithRef<typeof Sheet> {
+}
+
+export function CreateTemplateSheet({ ...props }: CreateTemplateSheetProps) {
+    const [isCreatePending, startCreateTransition] = React.useTransition()
+    const router = useRouter()
+    const { data: session } = useSession();
+
+    // 또는 더 안전하게
+    if (!session?.user?.id) {
+        toast.error("로그인이 필요합니다")
+        return
+    }
+
+    const form = useForm<CreateTemplateSchema>({
+        resolver: zodResolver(createTemplateSchema),
+        defaultValues: {
+            name: "",
+            slug: "",
+            description: "",
+            category: undefined, // 기본값을 undefined로 설정
+        },
+    })
+
+    // 이름 입력 시 자동으로 slug 생성
+    const watchedName = form.watch("name")
+    React.useEffect(() => {
+        if (watchedName && !form.formState.dirtyFields.slug) {
+            const autoSlug = watchedName
+                .toLowerCase()
+                .replace(/[^a-z0-9\s-]/g, '') // 특수문자 제거
+                .replace(/\s+/g, '-') // 공백을 하이픈으로
+                .replace(/-+/g, '-') // 연속 하이픈 제거
+                .trim()
+                .slice(0, 50) // 최대 50자
+
+            form.setValue("slug", autoSlug, { shouldValidate: false })
+        }
+    }, [watchedName, form])
+
+    // 기본 템플릿 내용 생성
+    const getDefaultContent = (category: string, name: string) => {
+        const templates = {
+            'welcome-email': `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>환영합니다!</title>
+</head>
+<body>
+    <div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
+        <h1>안녕하세요, {{userName}}님!</h1>
+        <p>${name}에 오신 것을 환영합니다.</p>
+        <p>{{message}}</p>
+    </div>
+</body>
+</html>`,
+            'password-reset': `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>비밀번호 재설정</title>
+</head>
+<body>
+    <div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
+        <h1>비밀번호 재설정</h1>
+        <p>안녕하세요, {{userName}}님.</p>
+        <p>비밀번호 재설정을 위해 아래 링크를 클릭해주세요.</p>
+        <a href="{{resetLink}}">비밀번호 재설정</a>
+    </div>
+</body>
+</html>`,
+            'notification': `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>알림</title>
+</head>
+<body>
+    <div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
+        <h1>알림</h1>
+        <p>안녕하세요, {{userName}}님.</p>
+        <p>{{message}}</p>
+    </div>
+</body>
+</html>`,
+        }
+
+        return templates[category as keyof typeof templates] || `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>${name}</title>
+</head>
+<body>
+    <div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
+        <h1>${name}</h1>
+        <p>안녕하세요, {{userName}}님.</p>
+        <p>{{message}}</p>
+    </div>
+</body>
+</html>`
+    }
+
+    // 기본 변수 생성
+    const getDefaultVariables = (category: string) => {
+        const variableTemplates = {
+            'welcome-email': [
+                { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' },
+                { variableName: 'email', variableType: 'string', isRequired: true, description: '사용자 이메일' },
+                { variableName: 'message', variableType: 'string', isRequired: false, description: '환영 메시지' },
+            ],
+            'password-reset': [
+                { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' },
+                { variableName: 'expiryTime', variableType: 'string', isRequired: true, description: '링크 유효 시간' },
+                { variableName: 'resetLink', variableType: 'string', isRequired: true, description: '재설정 URL' },
+                { variableName: 'supportEmail', variableType: 'string', isRequired: true, description: 'eVCP 서포터 이메일' },
+            ],
+            'notification': [
+                { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' },
+                { variableName: 'email', variableType: 'string', isRequired: true, description: '사용자 이메일' },
+                { variableName: 'message', variableType: 'string', isRequired: true, description: '알림 메시지' },
+            ],
+        }
+
+        return variableTemplates[category as keyof typeof variableTemplates] || [
+            { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' },
+            { variableName: 'message', variableType: 'string', isRequired: false, description: '메시지 내용' },
+        ]
+    }
+
+    const getDefaultSubject = (category: string, name: string) => {
+        const subjectTemplates = {
+            'welcome-email': '{{siteName}}에 오신 것을 환영합니다, {{userName}}님!',
+            'password-reset': '{{userName}}님의 비밀번호 재설정 요청',
+            'notification': '[{{notificationType}}] {{title}}',
+            'invoice': '{{companyName}} 인보이스 #{{invoiceNumber}}',
+            'marketing': '{{title}} - {{siteName}}',
+            'system': '[시스템] {{title}}'
+        }
+
+        return subjectTemplates[category as keyof typeof subjectTemplates] ||
+            `${name} - {{siteName}}`
+    }
+
+
+    function onSubmit(input: CreateTemplateSchema) {
+        startCreateTransition(async () => {
+            if (!session?.user?.id) {
+                toast.error("로그인이 필요합니다")
+                return
+            }
+
+
+            const defaultContent = getDefaultContent(input.category || '', input.name)
+            const defaultVariables = getDefaultVariables(input.category || '')
+            const defaultSubject = getDefaultSubject(input.category || '', input.name) 
+
+            const { error, data } = await createTemplateAction({
+                name: input.name,
+                slug: input.slug,
+                subject: defaultSubject,
+                content: defaultContent,
+                description: input.description,
+                category: input.category || undefined, // 빈 문자열 대신 undefined 전달
+                sampleData: {
+                    userName: '홍길동',
+                    email: 'user@example.com',
+                    message: '샘플 메시지입니다.',
+                },
+                createdBy: Number(session.user.id),
+                variables: defaultVariables,
+            })
+
+            if (error) {
+                toast.error(error)
+                return
+            }
+
+            form.reset()
+            props.onOpenChange?.(false)
+            toast.success("템플릿이 생성되었습니다")
+
+            // 생성된 템플릿의 세부 페이지로 이동
+            if (data?.slug) {
+                router.push(`/evcp/email-template/${data.slug}`)
+            } else {
+                window.location.reload()
+            }
+        })
+    }
+
+    return (
+        <Sheet {...props}>
+            <SheetContent className="flex flex-col gap-6 sm:max-w-md">
+                <SheetHeader className="text-left">
+                    <SheetTitle>새 템플릿 생성</SheetTitle>
+                    <SheetDescription>
+                        새로운 이메일 템플릿을 생성합니다. 기본 구조가 자동으로 생성되며, 생성 후 세부 내용을 편집할 수 있습니다.
+                    </SheetDescription>
+                </SheetHeader>
+                <Form {...form}>
+                    <form
+                        onSubmit={form.handleSubmit(onSubmit)}
+                        className="flex flex-col gap-4"
+                    >
+                        <FormField
+                            control={form.control}
+                            name="name"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>템플릿 이름</FormLabel>
+                                    <FormControl>
+                                        <Input
+                                            placeholder="예: 신규 회원 환영 메일"
+                                            {...field}
+                                        />
+                                    </FormControl>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+
+                        <FormField
+                            control={form.control}
+                            name="slug"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>Slug</FormLabel>
+                                    <FormControl>
+                                        <Input
+                                            placeholder="예: welcome-new-member"
+                                            {...field}
+                                        />
+                                    </FormControl>
+                                    <FormDescription>
+                                        URL에 사용될 고유 식별자입니다. 소문자, 숫자, 하이픈만 사용 가능합니다.
+                                    </FormDescription>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+
+                        <FormField
+                            control={form.control}
+                            name="category"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>카테고리</FormLabel>
+                                    <Select
+                                        onValueChange={(value) => {
+                                            // "none" 값이 선택되면 undefined로 설정
+                                            field.onChange(value === "none" ? undefined : value)
+                                        }}
+                                        value={field.value || "none"} // undefined인 경우 "none"으로 표시
+                                    >
+                                        <FormControl>
+                                            <SelectTrigger>
+                                                <SelectValue placeholder="카테고리를 선택하세요" />
+                                            </SelectTrigger>
+                                        </FormControl>
+                                        <SelectContent>
+                                            <SelectItem value="none">카테고리 없음</SelectItem>
+                                            {TEMPLATE_CATEGORY_OPTIONS.map((option) => (
+                                                <SelectItem key={option.value} value={option.value}>
+                                                    {option.label}
+                                                </SelectItem>
+                                            ))}
+                                        </SelectContent>
+                                    </Select>
+                                    <FormDescription>
+                                        카테고리에 따라 기본 템플릿과 변수가 자동으로 생성됩니다.
+                                    </FormDescription>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+
+                        <FormField
+                            control={form.control}
+                            name="description"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>설명 (선택사항)</FormLabel>
+                                    <FormControl>
+                                        <Textarea
+                                            placeholder="템플릿에 대한 설명을 입력하세요"
+                                            className="min-h-[80px]"
+                                            {...field}
+                                        />
+                                    </FormControl>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+
+                        <SheetFooter className="gap-2 pt-2 sm:space-x-0">
+                            <SheetClose asChild>
+                                <Button type="button" variant="outline">
+                                    취소
+                                </Button>
+                            </SheetClose>
+                            <Button disabled={isCreatePending}>
+                                {isCreatePending && (
+                                    <Loader
+                                        className="mr-2 size-4 animate-spin"
+                                        aria-hidden="true"
+                                    />
+                                )}
+                                생성 후 편집하기
+                            </Button>
+                        </SheetFooter>
+                    </form>
+                </Form>
+            </SheetContent>
+        </Sheet>
+    )
+}
\ No newline at end of file
-- 
cgit v1.2.3

