summaryrefslogtreecommitdiff
path: root/lib/email-template/editor/template-settings.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-30 05:04:33 +0000
committerjoonhoekim <26rote@gmail.com>2025-09-30 05:04:33 +0000
commitfb20768fa881841d3f80d12a276a9445feb6f514 (patch)
treedfc51a2be68136b1b67d9caca8fc24a9c2c78e20 /lib/email-template/editor/template-settings.tsx
parentf9afa89a4f27283f5b115cd89ececa08145b5c89 (diff)
(고건) 이메일 템플릿 정보 수정/복제 기능 에러 수정
Diffstat (limited to 'lib/email-template/editor/template-settings.tsx')
-rw-r--r--lib/email-template/editor/template-settings.tsx205
1 files changed, 100 insertions, 105 deletions
diff --git a/lib/email-template/editor/template-settings.tsx b/lib/email-template/editor/template-settings.tsx
index f253f87d..99ef5443 100644
--- a/lib/email-template/editor/template-settings.tsx
+++ b/lib/email-template/editor/template-settings.tsx
@@ -1,24 +1,6 @@
-"use client"
+'use client';
-import * as React from "react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Textarea } from "@/components/ui/textarea"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card"
+/* IMPORT */
import {
AlertDialog,
AlertDialogAction,
@@ -29,68 +11,92 @@ import {
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Badge } from "@/components/ui/badge"
-import { Separator } from "@/components/ui/separator"
+} from '@/components/ui/alert-dialog';
import {
- Save,
- Trash2,
AlertTriangle,
- Info,
Calendar,
- User,
+ Copy,
Hash,
- Copy
-} from "lucide-react"
-import { toast } from "sonner"
-import { useRouter } from "next/navigation"
-import { type TemplateWithVariables } from "@/db/schema"
-import { deleteTemplate, duplicateTemplate, updateTemplateAction } from "../service"
-import { TEMPLATE_CATEGORY_OPTIONS, getCategoryDisplayName } from "../validations"
+ Info,
+ Save,
+ Trash2,
+ User,
+} from 'lucide-react';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { deleteTemplate, duplicateTemplate, getCurrentUserId, updateTemplateAction } from '../service';
+import { getCategoryDisplayName, TEMPLATE_CATEGORY_OPTIONS } from '../validations';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Separator } from '@/components/ui/separator';
+import { Textarea } from '@/components/ui/textarea';
+import { toast } from 'sonner';
+import { type TemplateWithVariables } from '@/db/schema';
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+// ----------------------------------------------------------------------------------------------------
+/* TYPES */
interface TemplateSettingsProps {
- template: TemplateWithVariables
- onUpdate: (template: TemplateWithVariables) => void
+ template: TemplateWithVariables;
+ onUpdate: (template: TemplateWithVariables) => void;
}
+// ----------------------------------------------------------------------------------------------------
+
export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps) {
- const router = useRouter()
- const [isLoading, setIsLoading] = React.useState(false)
- const [formData, setFormData] = React.useState({
+ const router = useRouter();
+ const [isLoading, setIsLoading] = useState(false);
+ const [formData, setFormData] = useState({
name: template.name,
description: template.description || '',
category: template.category || '',
sampleData: JSON.stringify(template.sampleData || {}, null, 2)
- })
+ });
// 폼 데이터 업데이트
const updateFormData = (field: keyof typeof formData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }))
- }
+ };
// 기본 정보 저장
const handleSaveBasicInfo = async () => {
- setIsLoading(true)
+ setIsLoading(true);
try {
// 샘플 데이터 JSON 파싱 검증
- let parsedSampleData = {}
+ let parsedSampleData = {};
if (formData.sampleData.trim()) {
try {
- parsedSampleData = JSON.parse(formData.sampleData)
+ parsedSampleData = JSON.parse(formData.sampleData);
} catch (error) {
- toast.error('샘플 데이터 JSON 형식이 올바르지 않습니다.')
- setIsLoading(false)
- return
+ toast.error('샘플 데이터 JSON 형식이 올바르지 않습니다.');
+ setIsLoading(false);
+ return;
}
}
const result = await updateTemplateAction(template.slug, {
name: formData.name,
description: formData.description || undefined,
+ category: formData.category,
sampleData: parsedSampleData,
- updatedBy: 'current-user-id' // TODO: 실제 사용자 ID
- })
+ updatedBy: await getCurrentUserId(),
+ });
if (result.success) {
toast.success('템플릿 설정이 저장되었습니다.')
@@ -98,62 +104,63 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
...template,
name: formData.name,
description: formData.description,
+ category: formData.category,
sampleData: parsedSampleData,
- version: template.version + 1
- })
+ version: template.version ? template.version + 1 : 1,
+ });
} else {
- toast.error(result.error || '저장에 실패했습니다.')
+ toast.error(result.error || '저장에 실패했습니다.');
}
} catch (error) {
- toast.error('저장 중 오류가 발생했습니다.')
+ toast.error('저장 중 오류가 발생했습니다.');
} finally {
- setIsLoading(false)
+ setIsLoading(false);
}
}
// 템플릿 복제
const handleDuplicate = async () => {
- setIsLoading(true)
+ setIsLoading(true);
try {
- const copyName = `${template.name} (복사본)`
- const copySlug = `${template.slug}-copy-${Date.now()}`
+ const copyName = `${template.name} (복사본)`;
+ const copySlug = `${template.slug}-copy-${Date.now()}`;
const result = await duplicateTemplate(
template.id,
copyName,
copySlug,
- 'current-user-id' // TODO: 실제 사용자 ID
- )
+ await getCurrentUserId(),
+ );
if (result.success && result.data) {
- toast.success('템플릿이 복제되었습니다.')
- router.push(`/evcp/templates/${result.data.slug}`)
+ toast.success('템플릿이 복제되었습니다.');
+ router.push(`/evcp/email-template/${result.data.slug}`);
} else {
- toast.error(result.error || '복제에 실패했습니다.')
+ toast.error(result.error || '복제에 실패했습니다.');
}
} catch (error) {
- toast.error('복제 중 오류가 발생했습니다.')
+ toast.error('복제 중 오류가 발생했습니다.');
} finally {
- setIsLoading(false)
+ setIsLoading(false);
}
}
// 템플릿 삭제
const handleDelete = async () => {
- setIsLoading(true)
+ setIsLoading(true);
try {
- const result = await deleteTemplate(template.id)
+ const result = await deleteTemplate(template.id);
if (result.success) {
- toast.success('템플릿이 삭제되었습니다.')
- router.push('/evcp/templates')
+ toast.success('템플릿이 삭제되었습니다.');
+ router.push('/evcp/email-template');
} else {
- toast.error(result.error || '삭제에 실패했습니다.')
+ toast.error(result.error || '삭제에 실패했습니다.');
}
} catch (error) {
- toast.error('삭제 중 오류가 발생했습니다.')
+ toast.error('삭제 중 오류가 발생했습니다.');
} finally {
- setIsLoading(false)
+ setIsLoading(false);
}
}
@@ -215,7 +222,6 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
placeholder="템플릿 이름을 입력하세요"
/>
</div>
-
<div>
<Label htmlFor="description">설명</Label>
<Textarea
@@ -226,30 +232,28 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
className="min-h-[100px]"
/>
</div>
-
<div>
- <Label htmlFor="category">카테고리</Label>
- <Select
- value={formData.category || "none"} // 빈 문자열일 때 "none"으로 표시
- onValueChange={(value) => {
- // "none"이 선택되면 빈 문자열로 변환
- updateFormData('category', value === "none" ? "" : value)
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="카테고리를 선택하세요" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="none">카테고리 없음</SelectItem> {/* ✅ "none" 사용 */}
- {TEMPLATE_CATEGORY_OPTIONS.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
-</div>
-
+ <Label htmlFor="category">카테고리</Label>
+ <Select
+ value={formData.category || "none"} // 빈 문자열일 때 "none"으로 표시
+ onValueChange={(value) => {
+ // "none"이 선택되면 빈 문자열로 변환
+ updateFormData('category', value === "none" ? "" : value)
+ }}
+ >
+ <SelectTrigger>
+ <SelectValue placeholder="카테고리를 선택하세요" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value="none">카테고리 없음</SelectItem> {/* ✅ "none" 사용 */}
+ {TEMPLATE_CATEGORY_OPTIONS.map((option) => (
+ <SelectItem key={option.value} value={option.value}>
+ {option.label}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
<div className="flex justify-end">
<Button onClick={handleSaveBasicInfo} disabled={isLoading}>
<Save className="mr-2 h-4 w-4" />
@@ -284,7 +288,6 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
기본 데이터 생성
</Button>
</div>
-
<div>
<Label htmlFor="sampleData">샘플 데이터 (JSON)</Label>
<Textarea
@@ -295,7 +298,6 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
className="min-h-[200px] font-mono text-sm"
/>
</div>
-
<div className="bg-blue-50 p-3 rounded-lg">
<p className="text-sm text-blue-800">
<Info className="inline h-4 w-4 mr-1" />
@@ -323,21 +325,18 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
{template.slug}
</code>
</div>
-
<div className="flex items-center gap-2">
<Badge variant="outline">버전 {template.version}</Badge>
<Badge variant={template.category ? "default" : "secondary"}>
{getCategoryDisplayName(template.category)}
</Badge>
</div>
-
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className="text-sm">
생성일: {new Date(template.createdAt).toLocaleString('ko-KR')}
</span>
</div>
-
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className="text-sm">
@@ -345,7 +344,6 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
</span>
</div>
</div>
-
<div className="space-y-3">
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-muted-foreground" />
@@ -353,19 +351,16 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
생성자: {template.createdBy}
</span>
</div>
-
<div>
<span className="text-sm font-medium">변수 개수:</span>
<span className="ml-2 text-sm">{template.variables.length}개</span>
</div>
-
<div>
<span className="text-sm font-medium">필수 변수:</span>
<span className="ml-2 text-sm">
{template.variables.filter(v => v.isRequired).length}개
</span>
</div>
-
<div>
<span className="text-sm font-medium">콘텐츠 길이:</span>
<span className="ml-2 text-sm">{template.content.length} 문자</span>
@@ -471,4 +466,4 @@ export function TemplateSettings({ template, onUpdate }: TemplateSettingsProps)
</div>
</div>
)
-} \ No newline at end of file
+}