ERP-node/frontend/components/mail/MailAccountTable.tsx

264 lines
9.8 KiB
TypeScript

"use client";
import React, { useState } from 'react';
import {
Mail,
Edit2,
Trash2,
Power,
PowerOff,
Search,
Calendar,
Zap,
CheckCircle,
XCircle,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { MailAccount } from '@/lib/api/mail';
interface MailAccountTableProps {
accounts: MailAccount[];
onEdit: (account: MailAccount) => void;
onDelete: (account: MailAccount) => void;
onToggleStatus: (account: MailAccount) => void;
onTestConnection: (account: MailAccount) => void;
}
export default function MailAccountTable({
accounts,
onEdit,
onDelete,
onToggleStatus,
onTestConnection,
}: MailAccountTableProps) {
const [searchTerm, setSearchTerm] = useState('');
const [sortField, setSortField] = useState<keyof MailAccount>('createdAt');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
// 검색 필터링
const filteredAccounts = accounts.filter(
(account) =>
account.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
account.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
account.smtpHost.toLowerCase().includes(searchTerm.toLowerCase())
);
// 정렬
const sortedAccounts = [...filteredAccounts].sort((a, b) => {
const aValue = a[sortField];
const bValue = b[sortField];
if (typeof aValue === 'string' && typeof bValue === 'string') {
return sortOrder === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
return sortOrder === 'asc' ? aValue - bValue : bValue - aValue;
}
return 0;
});
const handleSort = (field: keyof MailAccount) => {
if (sortField === field) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortOrder('asc');
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
};
if (accounts.length === 0) {
return (
<div className="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-12 text-center border-2 border-dashed border-gray-300">
<Mail className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<p className="text-lg font-medium text-muted-foreground mb-2">
</p>
<p className="text-sm text-gray-500">
"새 계정 추가" .
</p>
</div>
);
}
return (
<div className="space-y-4">
{/* 검색 */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="계정명, 이메일, 서버로 검색..."
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-all"
/>
</div>
{/* 테이블 */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gradient-to-r from-slate-50 to-gray-50 border-b border-gray-200">
<tr>
<th
className="px-6 py-4 text-left text-sm font-semibold text-gray-700 cursor-pointer hover:bg-gray-100 transition"
onClick={() => handleSort('name')}
>
<div className="flex items-center gap-2">
<Mail className="w-4 h-4 text-orange-500" />
{sortField === 'name' && (
<span className="text-xs">
{sortOrder === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th
className="px-6 py-4 text-left text-sm font-semibold text-gray-700 cursor-pointer hover:bg-gray-100 transition"
onClick={() => handleSort('email')}
>
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700">
SMTP
</th>
<th
className="px-6 py-4 text-center text-sm font-semibold text-gray-700 cursor-pointer hover:bg-gray-100 transition"
onClick={() => handleSort('status')}
>
</th>
<th
className="px-6 py-4 text-center text-sm font-semibold text-gray-700 cursor-pointer hover:bg-gray-100 transition"
onClick={() => handleSort('dailyLimit')}
>
<div className="flex items-center justify-center gap-2">
<Zap className="w-4 h-4 text-orange-500" />
</div>
</th>
<th
className="px-6 py-4 text-center text-sm font-semibold text-gray-700 cursor-pointer hover:bg-gray-100 transition"
onClick={() => handleSort('createdAt')}
>
<div className="flex items-center justify-center gap-2">
<Calendar className="w-4 h-4 text-orange-500" />
</div>
</th>
<th className="px-6 py-4 text-center text-sm font-semibold text-gray-700">
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{sortedAccounts.map((account) => (
<tr
key={account.id}
className="hover:bg-orange-50/50 transition-colors"
>
<td className="px-6 py-4">
<div className="font-medium text-gray-900">{account.name}</div>
</td>
<td className="px-6 py-4">
<div className="text-sm text-muted-foreground">{account.email}</div>
</td>
<td className="px-6 py-4">
<div className="text-sm text-muted-foreground">
{account.smtpHost}:{account.smtpPort}
</div>
<div className="text-xs text-gray-400">
{account.smtpSecure ? 'SSL' : 'TLS'}
</div>
</td>
<td className="px-6 py-4 text-center">
<button
onClick={() => onToggleStatus(account)}
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all hover:scale-105 ${
account.status === 'active'
? 'bg-green-100 text-green-700 hover:bg-green-200'
: 'bg-gray-100 text-muted-foreground hover:bg-gray-200'
}`}
>
{account.status === 'active' ? (
<>
<CheckCircle className="w-3.5 h-3.5" />
</>
) : (
<>
<XCircle className="w-3.5 h-3.5" />
</>
)}
</button>
</td>
<td className="px-6 py-4 text-center">
<div className="text-sm font-medium text-gray-900">
{account.dailyLimit > 0
? account.dailyLimit.toLocaleString()
: '무제한'}
</div>
</td>
<td className="px-6 py-4 text-center">
<div className="text-sm text-muted-foreground">
{formatDate(account.createdAt)}
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center justify-center gap-2">
<button
onClick={() => onTestConnection(account)}
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="SMTP 연결 테스트"
>
<Zap className="w-4 h-4" />
</button>
<button
onClick={() => onEdit(account)}
className="p-2 text-primary hover:bg-accent rounded-lg transition-colors"
title="수정"
>
<Edit2 className="w-4 h-4" />
</button>
<button
onClick={() => onDelete(account)}
className="p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
title="삭제"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* 결과 요약 */}
<div className="text-sm text-muted-foreground text-center">
{accounts.length} {sortedAccounts.length}
{searchTerm && ` (검색: "${searchTerm}")`}
</div>
</div>
);
}