diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-30 05:04:33 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-30 05:04:33 +0000 |
| commit | fb20768fa881841d3f80d12a276a9445feb6f514 (patch) | |
| tree | dfc51a2be68136b1b67d9caca8fc24a9c2c78e20 /lib/email-template/editor/template-settings.tsx | |
| parent | f9afa89a4f27283f5b115cd89ececa08145b5c89 (diff) | |
(고건) 이메일 템플릿 정보 수정/복제 기능 에러 수정
Diffstat (limited to 'lib/email-template/editor/template-settings.tsx')
| -rw-r--r-- | lib/email-template/editor/template-settings.tsx | 205 |
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 +} |
