202 lines
6.9 KiB
TypeScript
202 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { getSentMailList, updateDraft, deleteSentMail, bulkDeleteMails, type SentMailHistory } from "@/lib/api/mail";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Edit, Trash2, Loader2, Mail } from "lucide-react";
|
|
import { format } from "date-fns";
|
|
import { ko } from "date-fns/locale";
|
|
|
|
export default function DraftsPage() {
|
|
const router = useRouter();
|
|
const [drafts, setDrafts] = useState<SentMailHistory[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [deleting, setDeleting] = useState<string | null>(null);
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
const [bulkDeleting, setBulkDeleting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadDrafts();
|
|
}, []);
|
|
|
|
const loadDrafts = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await getSentMailList({
|
|
status: "draft",
|
|
sortBy: "updatedAt",
|
|
sortOrder: "desc",
|
|
});
|
|
// console.log('📋 임시 저장 목록 조회:', response);
|
|
// console.log('📋 임시 저장 개수:', response.items.length);
|
|
setDrafts(response.items);
|
|
} catch (error) {
|
|
// console.error("❌ 임시 저장 메일 로드 실패:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleEdit = (draft: SentMailHistory) => {
|
|
// 임시 저장 메일을 메일 발송 페이지로 전달
|
|
const params = new URLSearchParams({
|
|
draftId: draft.id,
|
|
to: draft.to.join(","),
|
|
cc: draft.cc?.join(",") || "",
|
|
bcc: draft.bcc?.join(",") || "",
|
|
subject: draft.subject,
|
|
content: draft.htmlContent,
|
|
accountId: draft.accountId,
|
|
});
|
|
router.push(`/admin/mail/send?${params.toString()}`);
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!confirm("이 임시 저장 메일을 삭제하시겠습니까?")) return;
|
|
|
|
try {
|
|
setDeleting(id);
|
|
await deleteSentMail(id);
|
|
setDrafts(drafts.filter((d) => d.id !== id));
|
|
setSelectedIds(selectedIds.filter((selectedId) => selectedId !== id));
|
|
} catch (error) {
|
|
// console.error("임시 저장 메일 삭제 실패:", error);
|
|
alert("삭제에 실패했습니다.");
|
|
} finally {
|
|
setDeleting(null);
|
|
}
|
|
};
|
|
|
|
const handleBulkDelete = async () => {
|
|
if (selectedIds.length === 0) {
|
|
alert("삭제할 메일을 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`선택한 ${selectedIds.length}개의 임시 저장 메일을 삭제하시겠습니까?`)) return;
|
|
|
|
try {
|
|
setBulkDeleting(true);
|
|
const result = await bulkDeleteMails(selectedIds);
|
|
setDrafts(drafts.filter((d) => !selectedIds.includes(d.id)));
|
|
setSelectedIds([]);
|
|
alert(result.message);
|
|
} catch (error) {
|
|
// console.error("일괄 삭제 실패:", error);
|
|
alert("일괄 삭제에 실패했습니다.");
|
|
} finally {
|
|
setBulkDeleting(false);
|
|
}
|
|
};
|
|
|
|
const handleSelectAll = () => {
|
|
if (selectedIds.length === drafts.length) {
|
|
setSelectedIds([]);
|
|
} else {
|
|
setSelectedIds(drafts.map((d) => d.id));
|
|
}
|
|
};
|
|
|
|
const handleSelectOne = (id: string) => {
|
|
if (selectedIds.includes(id)) {
|
|
setSelectedIds(selectedIds.filter((selectedId) => selectedId !== id));
|
|
} else {
|
|
setSelectedIds([...selectedIds, id]);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-3 space-y-3">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-foreground">임시보관함</h1>
|
|
<p className="mt-2 text-muted-foreground">작성 중인 메일이 자동으로 저장됩니다</p>
|
|
</div>
|
|
|
|
{drafts.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="flex flex-col items-center justify-center py-12">
|
|
<Mail className="w-12 h-12 text-muted-foreground mb-4" />
|
|
<p className="text-muted-foreground">임시 저장된 메일이 없습니다</p>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="grid gap-3">
|
|
{drafts.map((draft) => (
|
|
<Card key={draft.id} className="hover:shadow-md transition-shadow">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1 min-w-0">
|
|
<CardTitle className="text-lg truncate">
|
|
{draft.subject || "(제목 없음)"}
|
|
</CardTitle>
|
|
<CardDescription className="mt-1">
|
|
받는 사람: {draft.to.join(", ") || "(없음)"}
|
|
</CardDescription>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleEdit(draft)}
|
|
className="h-8"
|
|
>
|
|
<Edit className="w-4 h-4 mr-1" />
|
|
편집
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => handleDelete(draft.id)}
|
|
disabled={deleting === draft.id}
|
|
className="h-8"
|
|
>
|
|
{deleting === draft.id ? (
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<>
|
|
<Trash2 className="w-4 h-4 mr-1" />
|
|
삭제
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
<span>계정: {draft.accountName || draft.accountEmail}</span>
|
|
<span>
|
|
{draft.updatedAt
|
|
? format(new Date(draft.updatedAt), "yyyy-MM-dd HH:mm", { locale: ko })
|
|
: format(new Date(draft.sentAt), "yyyy-MM-dd HH:mm", { locale: ko })}
|
|
</span>
|
|
</div>
|
|
{draft.htmlContent && (
|
|
<div
|
|
className="mt-2 text-sm text-muted-foreground line-clamp-2"
|
|
dangerouslySetInnerHTML={{
|
|
__html: draft.htmlContent.replace(/<[^>]*>/g, "").substring(0, 100),
|
|
}}
|
|
/>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|