Files
chat_room/src/app/(dashboard)/backups/page.tsx
T

130 lines
4.7 KiB
TypeScript
Raw Normal View History

"use client";
import { useCallback, useEffect, useState } from "react";
import { Database, Download, HardDrive, RefreshCw } from "lucide-react";
import { Card } from "@/components/ui/Card";
import { Button } from "@/components/ui/Button";
import { ErrorBanner } from "@/components/ui/ErrorBanner";
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
import { useToast } from "@/components/ui/Toast";
import { formatApiError } from "@/lib/hooks";
import { BackupInfo, listBackups, createBackup, downloadBackup } from "@/lib/api";
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
export default function BackupsPage() {
const toast = useToast();
const [backups, setBackups] = useState<BackupInfo[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [creating, setCreating] = useState(false);
const [downloadingFile, setDownloadingFile] = useState<string | null>(null);
const load = useCallback(async () => {
try {
setBackups(await listBackups());
setError(null);
} catch (err) {
setError(formatApiError(err));
}
}, []);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
load();
}, [load]);
async function handleCreate() {
setCreating(true);
try {
await createBackup();
toast.showSuccess("بک‌آپ جدید با موفقیت ساخته شد");
await load();
} catch (err) {
toast.showError(formatApiError(err));
} finally {
setCreating(false);
}
}
async function handleDownload(filename: string) {
setDownloadingFile(filename);
try {
await downloadBackup(filename);
} catch (err) {
toast.showError(formatApiError(err));
} finally {
setDownloadingFile(null);
}
}
if (!backups && !error) return <FullScreenLoader label="لیست بک‌آپ‌ها رو میاریم..." />;
return (
<div className="flex flex-col gap-6 max-w-3xl">
<div className="flex items-center justify-between flex-wrap gap-3">
<div>
<h1 className="font-[var(--font-display)] font-extrabold text-[26px]">
بک‌آپ دیتابیس 💾
</h1>
<p className="text-[14px] text-(--color-content-secondary) mt-1">
یک نسخه‌ی کامل و فشرده از دیتابیس بگیر یا نسخه‌های قبلی رو دانلود کن.
</p>
</div>
<div className="flex items-center gap-2">
<Button variant="tertiary" size="sm" icon={<RefreshCw size={14} />} onClick={load}>
تازه‌سازی
</Button>
<Button size="sm" icon={<Database size={15} />} isLoading={creating} onClick={handleCreate}>
ساخت بک‌آپ جدید
</Button>
</div>
</div>
{error && <ErrorBanner message={error} />}
<Card padded={false} className="overflow-hidden">
{!backups || backups.length === 0 ? (
<div className="flex flex-col items-center gap-2 py-14 text-center">
<HardDrive size={28} className="text-(--color-content-tertiary)" />
<p className="text-[14px] font-bold text-(--color-content-primary)">
هنوز هیچ بک‌آپی گرفته نشده
</p>
<p className="text-[13px] text-(--color-content-tertiary)">
با دکمه‌ی «ساخت بک‌آپ جدید» اولین نسخه رو بگیر.
</p>
</div>
) : (
<div className="divide-y divide-(--color-border-subtle)">
{backups.map((b) => (
<div key={b.filename} className="flex items-center justify-between px-5 py-4 gap-3">
<div className="min-w-0">
<p dir="ltr" className="font-bold text-[13px] text-(--color-content-primary) truncate">
{b.filename}
</p>
<p className="text-[12px] text-(--color-content-tertiary) mt-0.5">
{new Date(b.created_at).toLocaleString("fa-IR")} · {formatSize(b.size_bytes)}
</p>
</div>
<Button
variant="tertiary"
size="sm"
icon={<Download size={14} />}
isLoading={downloadingFile === b.filename}
onClick={() => handleDownload(b.filename)}
className="shrink-0"
>
دانلود
</Button>
</div>
))}
</div>
)}
</Card>
</div>
);
}