357 lines
12 KiB
TypeScript
357 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Mail,
|
|
Send,
|
|
Inbox,
|
|
FileText,
|
|
RefreshCw,
|
|
TrendingUp,
|
|
Users,
|
|
Calendar,
|
|
ArrowRight,
|
|
Trash2,
|
|
Edit
|
|
} from "lucide-react";
|
|
import { getMailAccounts, getMailTemplates, getMailStatistics, getTodayReceivedCount } from "@/lib/api/mail";
|
|
import MailNotifications from "@/components/mail/MailNotifications";
|
|
|
|
interface DashboardStats {
|
|
totalAccounts: number;
|
|
totalTemplates: number;
|
|
sentToday: number;
|
|
receivedToday: number;
|
|
sentThisMonth: number;
|
|
successRate: number;
|
|
}
|
|
|
|
export default function MailDashboardPage() {
|
|
const [stats, setStats] = useState<DashboardStats>({
|
|
totalAccounts: 0,
|
|
totalTemplates: 0,
|
|
sentToday: 0,
|
|
receivedToday: 0,
|
|
sentThisMonth: 0,
|
|
successRate: 0,
|
|
});
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const loadStats = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const accounts = await getMailAccounts();
|
|
const templates = await getMailTemplates();
|
|
|
|
// 메일 통계 조회 (실패 시 기본값 사용)
|
|
let mailStats = {
|
|
todayCount: 0,
|
|
thisMonthCount: 0,
|
|
successRate: 0,
|
|
};
|
|
|
|
try {
|
|
const stats = await getMailStatistics();
|
|
if (stats && typeof stats === 'object') {
|
|
mailStats = {
|
|
todayCount: stats.todayCount || 0,
|
|
thisMonthCount: stats.thisMonthCount || 0,
|
|
successRate: stats.successRate || 0,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
// console.error('메일 통계 조회 실패:', error);
|
|
// 기본값 사용
|
|
}
|
|
|
|
// 오늘 수신 메일 수 조회 (IMAP 실시간 조회)
|
|
let receivedTodayCount = 0;
|
|
try {
|
|
receivedTodayCount = await getTodayReceivedCount();
|
|
} catch (error) {
|
|
// console.error('수신 메일 수 조회 실패:', error);
|
|
// 실패 시 0으로 표시
|
|
}
|
|
|
|
setStats({
|
|
totalAccounts: accounts.length,
|
|
totalTemplates: templates.length,
|
|
sentToday: mailStats.todayCount,
|
|
receivedToday: receivedTodayCount,
|
|
sentThisMonth: mailStats.thisMonthCount,
|
|
successRate: mailStats.successRate,
|
|
});
|
|
} catch (error) {
|
|
// console.error('통계 로드 실패:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadStats();
|
|
}, []);
|
|
|
|
const statCards = [
|
|
{
|
|
title: "등록된 계정",
|
|
value: stats.totalAccounts,
|
|
icon: Users,
|
|
color: "blue",
|
|
bgColor: "bg-blue-100",
|
|
iconColor: "text-blue-600",
|
|
href: "/admin/mail/accounts",
|
|
},
|
|
{
|
|
title: "템플릿 수",
|
|
value: stats.totalTemplates,
|
|
icon: FileText,
|
|
color: "green",
|
|
bgColor: "bg-green-100",
|
|
iconColor: "text-green-600",
|
|
href: "/admin/mail/templates",
|
|
},
|
|
{
|
|
title: "오늘 발송",
|
|
value: stats.sentToday,
|
|
icon: Send,
|
|
color: "orange",
|
|
bgColor: "bg-orange-100",
|
|
iconColor: "text-orange-600",
|
|
href: "/admin/mail/sent",
|
|
},
|
|
{
|
|
title: "오늘 수신",
|
|
value: stats.receivedToday,
|
|
icon: Inbox,
|
|
color: "purple",
|
|
bgColor: "bg-purple-100",
|
|
iconColor: "text-purple-600",
|
|
href: "/admin/mail/receive",
|
|
},
|
|
];
|
|
|
|
const quickLinks = [
|
|
{
|
|
title: "계정 관리",
|
|
description: "메일 계정 설정",
|
|
href: "/admin/mail/accounts",
|
|
icon: Users,
|
|
color: "blue",
|
|
},
|
|
{
|
|
title: "템플릿 관리",
|
|
description: "템플릿 편집",
|
|
href: "/admin/mail/templates",
|
|
icon: FileText,
|
|
color: "green",
|
|
},
|
|
{
|
|
title: "메일 발송",
|
|
description: "메일 보내기",
|
|
href: "/admin/mail/send",
|
|
icon: Send,
|
|
color: "orange",
|
|
},
|
|
{
|
|
title: "대량 발송",
|
|
description: "CSV로 대량 발송",
|
|
href: "/admin/mail/bulk-send",
|
|
icon: Users,
|
|
color: "teal",
|
|
},
|
|
{
|
|
title: "보낸메일함",
|
|
description: "발송 이력 확인",
|
|
href: "/admin/mail/sent",
|
|
icon: Mail,
|
|
color: "indigo",
|
|
},
|
|
{
|
|
title: "수신함",
|
|
description: "받은 메일 확인",
|
|
href: "/admin/mail/receive",
|
|
icon: Inbox,
|
|
color: "purple",
|
|
},
|
|
{
|
|
title: "임시 저장",
|
|
description: "작성 중인 메일",
|
|
href: "/admin/mail/drafts",
|
|
icon: Edit,
|
|
color: "amber",
|
|
},
|
|
{
|
|
title: "휴지통",
|
|
description: "삭제된 메일",
|
|
href: "/admin/mail/trash",
|
|
icon: Trash2,
|
|
color: "red",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<div className="w-full px-3 py-3 space-y-3">
|
|
{/* 페이지 제목 */}
|
|
<div className="flex items-center justify-between bg-card rounded-lg border p-8">
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-4 bg-primary/10 rounded-lg">
|
|
<Mail className="w-8 h-8 text-primary" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-foreground mb-1">메일 관리 대시보드</h1>
|
|
<p className="text-muted-foreground">메일 시스템의 전체 현황을 한눈에 확인하세요</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<MailNotifications />
|
|
<Button
|
|
variant="outline"
|
|
size="lg"
|
|
onClick={loadStats}
|
|
disabled={loading}
|
|
>
|
|
<RefreshCw className={`w-5 h-5 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
|
새로고침
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 통계 카드 */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
|
{statCards.map((stat, index) => (
|
|
<Link key={index} href={stat.href}>
|
|
<Card className="hover:shadow-md transition-all hover:scale-105 cursor-pointer">
|
|
<CardContent className="p-6">
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className="flex-1">
|
|
<p className="text-sm font-medium text-muted-foreground mb-3">
|
|
{stat.title}
|
|
</p>
|
|
<p className="text-4xl font-bold text-foreground">
|
|
{stat.value}
|
|
</p>
|
|
</div>
|
|
<div className="p-4 bg-muted rounded-lg">
|
|
<stat.icon className="w-7 h-7 text-muted-foreground" />
|
|
</div>
|
|
</div>
|
|
{/* 진행 바 */}
|
|
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-primary transition-all duration-1000"
|
|
style={{ width: `${Math.min((stat.value / 10) * 100, 100)}%` }}
|
|
></div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{/* 이번 달 통계 */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
|
<Card>
|
|
<CardHeader className="border-b">
|
|
<CardTitle className="text-lg flex items-center">
|
|
<div className="p-2 bg-muted rounded-lg mr-3">
|
|
<Calendar className="w-5 h-5 text-foreground" />
|
|
</div>
|
|
<span>이번 달 발송 통계</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-6">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
|
<span className="text-sm font-medium text-muted-foreground">총 발송 건수</span>
|
|
<span className="text-2xl font-bold text-foreground">{stats.sentThisMonth} 건</span>
|
|
</div>
|
|
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
|
<span className="text-sm font-medium text-muted-foreground">성공률</span>
|
|
<span className="text-2xl font-bold text-foreground">{stats.successRate}%</span>
|
|
</div>
|
|
{/* 전월 대비 통계는 현재 불필요하여 주석처리
|
|
<div className="flex items-center justify-between pt-3 border-t">
|
|
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
|
<TrendingUp className="w-4 h-4" />
|
|
전월 대비
|
|
</div>
|
|
<span className="text-lg font-bold text-foreground">+12%</span>
|
|
</div>
|
|
*/}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="border-b">
|
|
<CardTitle className="text-lg flex items-center">
|
|
<div className="p-2 bg-muted rounded-lg mr-3">
|
|
<Mail className="w-5 h-5 text-foreground" />
|
|
</div>
|
|
<span>시스템 상태</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-6">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-3 h-3 bg-primary rounded-full animate-pulse"></div>
|
|
<span className="text-sm font-medium text-muted-foreground">메일 서버</span>
|
|
</div>
|
|
<span className="text-sm font-bold text-foreground">정상 작동</span>
|
|
</div>
|
|
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-3 h-3 bg-primary rounded-full"></div>
|
|
<span className="text-sm font-medium text-muted-foreground">활성 계정</span>
|
|
</div>
|
|
<span className="text-lg font-bold text-foreground">{stats.totalAccounts} 개</span>
|
|
</div>
|
|
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-3 h-3 bg-primary rounded-full"></div>
|
|
<span className="text-sm font-medium text-muted-foreground">사용 가능 템플릿</span>
|
|
</div>
|
|
<span className="text-lg font-bold text-foreground">{stats.totalTemplates} 개</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* 빠른 액세스 */}
|
|
<Card>
|
|
<CardHeader className="border-b">
|
|
<CardTitle className="text-lg">빠른 액세스</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-6">
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{quickLinks.map((link, index) => (
|
|
<a
|
|
key={index}
|
|
href={link.href}
|
|
className="group flex items-center gap-4 p-5 rounded-lg border hover:border-primary/50 hover:shadow-md transition-all bg-card hover:bg-muted/50"
|
|
>
|
|
<div className="p-3 bg-muted rounded-lg group-hover:scale-105 transition-transform">
|
|
<link.icon className="w-6 h-6 text-muted-foreground" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-semibold text-foreground text-base mb-1">{link.title}</p>
|
|
<p className="text-sm text-muted-foreground truncate">{link.description}</p>
|
|
</div>
|
|
<ArrowRight className="w-5 h-5 text-muted-foreground group-hover:text-foreground group-hover:translate-x-1 transition-all" />
|
|
</a>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|