Files

221 lines
9.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { auth } from "@/auth";
import { getDomains } from "@/lib/mailcow";
import { canAccessDomain } from "@/lib/users";
import { formatBytes } from "@/lib/format";
import { getDictionary, Locale } from "@/app/dictionaries";
export default async function DashboardPage(
props: {
params: Promise<{ lang: string }>;
}
) {
const params = await props.params;
const session = await auth();
const role = session?.user?.role;
const userDomains = session?.user?.domains ?? [];
const lang = params.lang as Locale;
const dict = await getDictionary(lang);
const allDomains = await getDomains();
const visibleDomains = allDomains.filter((d) => canAccessDomain(userDomains, d.domain_name));
const totalMailboxes = visibleDomains.reduce((sum, d) => sum + d.mboxes_in_domain, 0);
const totalAliases = visibleDomains.reduce((sum, d) => sum + d.aliases_in_domain, 0);
return (
<>
<div className="page-header">
<div>
<h1 className="page-title">{dict.dashboard.title || "Dashboard"}</h1>
<p className="page-subtitle">{dict.dashboard.welcome || "Hoş geldiniz"}, {session?.user?.name} 👋</p>
</div>
</div>
<div className="page-body">
<div className="stats-grid">
<StatCard
label={dict.dashboard.totalDomains || "Toplam Domain"}
value={visibleDomains.length}
color="var(--accent)"
icon={<GlobeIcon />}
/>
<StatCard
label={dict.dashboard.mailboxes || "Mail Kutuları"}
value={totalMailboxes}
color="var(--success)"
icon={<MailIcon />}
/>
<StatCard
label={dict.dashboard.aliases || "Alias"}
value={totalAliases}
color="var(--warning)"
icon={<AtIcon />}
/>
{role === "SUPER_ADMIN" && (
<StatCard
label={dict.dashboard.users || "Tanımlı Kullanıcı"}
value={"—"}
sub={dict.dashboard.usersSub || "Kullanıcılar .env'den yönetilir"}
color="var(--text-muted)"
icon={<UsersIcon />}
/>
)}
</div>
{/* Domain durum tablosu */}
{visibleDomains.length > 0 && (
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
<div style={{ padding: "16px 20px", borderBottom: "1px solid var(--border)" }}>
<h2 style={{ fontSize: 14, fontWeight: 700, color: "var(--text-primary)" }}>
{dict.dashboard.domainStatus || "Domain Durumu"}
</h2>
</div>
<div className="table-wrap" style={{ border: "none", borderRadius: 0 }}>
<table>
<thead>
<tr>
<th>{dict.dashboard.domain || "Domain"}</th>
<th>{dict.dashboard.mailboxes || "Mail Kutuları"}</th>
<th>{dict.dashboard.quotaUsage || "Kota Kullanımı"}</th>
<th>{dict.dashboard.status || "Durum"}</th>
</tr>
</thead>
<tbody>
{visibleDomains.map((d) => {
const quotaUsed = Number(d.quota_used_in_domain);
const quotaTotal = d.max_quota_for_domain;
const pct = quotaTotal > 0 ? Math.min((quotaUsed / quotaTotal) * 100, 100) : 0;
return (
<tr key={d.domain_name}>
<td>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<div style={{ width: 28, height: 28, borderRadius: 6, background: "var(--accent-dim)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--accent-hover)", flexShrink: 0 }}>
<GlobeIcon />
</div>
<span style={{ fontWeight: 500 }}>{d.domain_name}</span>
</div>
</td>
<td>
<span>{d.mboxes_in_domain}</span>
<span style={{ color: "var(--text-muted)", fontSize: 12 }}> / {d.max_num_mboxes_for_domain}</span>
</td>
<td style={{ minWidth: 160 }}>
<div style={{ fontSize: 11, color: "var(--text-secondary)", marginBottom: 4 }}>
{formatBytes(quotaUsed)} / {formatBytes(quotaTotal)}
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<div className="progress-bar">
<div className={`progress-fill ${pct > 80 ? "danger" : ""}`} style={{ width: `${pct}%` }} />
</div>
<span style={{ fontSize: 11, color: "var(--text-muted)", flexShrink: 0 }}>{Math.round(pct)}%</span>
</div>
</td>
<td>
<span className={`badge ${String(d.active) === "1" ? "badge-green" : "badge-red"}`}>
{String(d.active) === "1" ? `${dict.dashboard.active || "Aktif"}` : `${dict.dashboard.inactive || "Pasif"}`}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
{/* Quick actions */}
<div className="card">
<h2 style={{ fontSize: 14, fontWeight: 700, marginBottom: 16, color: "var(--text-primary)" }}>
{dict.dashboard.quickActions || "Hızlı İşlemler"}
</h2>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
{role === "SUPER_ADMIN" && (
<QuickItem
href={`/${lang}/dashboard/domains`}
icon={<GlobeIcon />}
title={dict.dashboard.manageDomains || "Domain Yönetimi"}
desc={dict.dashboard.manageDomainsDesc || "Domain ekle, sil, yönet"}
color="var(--accent)"
/>
)}
<QuickItem
href={`/${lang}/dashboard/mailboxes`}
icon={<MailIcon />}
title={dict.dashboard.manageMailboxes || "Mail Hesapları"}
desc={dict.dashboard.manageMailboxesDesc || "Yeni hesap oluştur, şifre değiştir, sil"}
color="var(--success)"
/>
{role === "SUPER_ADMIN" && (
<QuickItem
href={`/${lang}/dashboard/users`}
icon={<UsersIcon />}
title={dict.dashboard.manageUsers || "Kullanıcılar"}
desc={dict.dashboard.manageUsersDesc || ".env'den tanımlı panel kullanıcılarını görüntüle"}
color="var(--warning)"
/>
)}
</div>
</div>
</div>
</>
);
}
function StatCard({ label, value, sub, color, icon }: {
label: string;
value: number | string;
sub?: string;
color: string;
icon: React.ReactNode;
}) {
return (
<div className="stat-card">
<div className="stat-icon" style={{ background: `${color}20`, color }}>
{icon}
</div>
<div className="stat-label">{label}</div>
<div className="stat-value">{value}</div>
{sub && <div style={{ fontSize: 11, color: "var(--text-muted)" }}>{sub}</div>}
</div>
);
}
function QuickItem({ href, icon, title, desc, color }: {
href: string;
icon: React.ReactNode;
title: string;
desc: string;
color: string;
}) {
return (
<a
href={href}
style={{
display: "flex", alignItems: "center", gap: 14, padding: 14,
borderRadius: "var(--radius)", border: "1px solid var(--border)",
background: "var(--bg)", textDecoration: "none",
transition: "all 0.15s ease",
}}
>
<div style={{ width: 36, height: 36, borderRadius: 8, background: `${color}20`, color, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
{icon}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, fontWeight: 600, color: "var(--text-primary)" }}>{title}</div>
<div style={{ fontSize: 12, color: "var(--text-secondary)", marginTop: 2 }}>{desc}</div>
</div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>
</a>
);
}
// Icons
function GlobeIcon() { return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/><path d="M2 12h20"/></svg>; }
function MailIcon() { return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>; }
function AtIcon() { return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"/></svg>; }
function UsersIcon() { return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>; }