Files
chinese-family-tree-2/app/settings/page.tsx
T
freedakgmail 626d7dddde 0.0.0.5
2025-11-23 00:49:16 +08:00

240 lines
10 KiB
TypeScript
Raw 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.
"use client"
import { SiteHeader } from "@/components/site-header"
import { useFamily } from "@/context/family-context"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Download, Upload, AlertTriangle, RefreshCw, FileText, Bell, History, Database } from "lucide-react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { useRef, useState } from "react"
import { exportToGedcom, importFromGedcom } from "@/lib/gedcom"
import { NotificationSettings } from "@/components/settings/notification-settings"
import { ActivityLogViewer } from "@/components/settings/activity-log-viewer"
import { upgradeDatabase } from "@/lib/db-upgrade"
import { ErrorBoundary } from "@/components/error-boundary"
export default function SettingsPage() {
const { treeData, loadData, resetData } = useFamily()
const [importStatus, setImportStatus] = useState<string>("")
const [gedcomStatus, setGedcomStatus] = useState<string>("")
const fileInputRef = useRef<HTMLInputElement>(null)
const gedcomInputRef = useRef<HTMLInputElement>(null)
return (
<div className="min-h-screen bg-background flex flex-col font-sans">
<SiteHeader />
<main className="container mx-auto py-8 px-4 md:px-6 flex-1 max-w-6xl">
<h1 className="text-3xl font-serif font-bold mb-8"> (Settings)</h1>
<div className="space-y-6">
{/* 通知设置和数据备份 - 两列布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<NotificationSettings />
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Download className="h-5 w-5" /> (Backup)
</CardTitle>
<CardDescription> JSON </CardDescription>
</CardHeader>
<CardContent>
<Button
onClick={() => {
const dataStr = JSON.stringify(treeData, null, 2)
const blob = new Blob([dataStr], { type: "application/json" })
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = `family_tree_backup_${new Date().toISOString().split("T")[0]}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}}
>
(Export JSON)
</Button>
</CardContent>
</Card>
</div>
{/* 数据恢复和 GEDCOM - 两列布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="h-5 w-5" /> (Restore)
</CardTitle>
<CardDescription> JSON </CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle></AlertTitle>
<AlertDescription></AlertDescription>
</Alert>
<div className="flex items-center gap-4">
<input
type="file"
ref={fileInputRef}
className="hidden"
accept=".json"
onChange={(e) => {
const file = e.target.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = async (event) => {
try {
const jsonStr = event.target?.result as string
const data = JSON.parse(jsonStr)
await loadData(data)
setImportStatus("导入成功!")
if (fileInputRef.current) fileInputRef.current.value = ""
} catch (err) {
console.error(err)
setImportStatus("错误:无法解析 JSON 文件")
}
}
reader.readAsText(file)
}}
/>
<Button variant="outline" onClick={() => fileInputRef.current?.click()}>
...
</Button>
<span className="text-sm text-muted-foreground">{importStatus}</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" /> GEDCOM
</CardTitle>
<CardDescription>/ GEDCOM AncestryMyHeritage</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Button
variant="outline"
onClick={() => {
const gedcomText = exportToGedcom(treeData)
const blob = new Blob([gedcomText], { type: "text/plain;charset=utf-8" })
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = `family_tree_${new Date().toISOString().split("T")[0]}.ged`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}}
>
<Download className="mr-2 h-4 w-4" />
GEDCOM
</Button>
<input
type="file"
ref={gedcomInputRef}
className="hidden"
accept=".ged,.gedcom"
onChange={(e) => {
const file = e.target.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = async (event) => {
try {
const gedcomText = event.target?.result as string
const importedData = importFromGedcom(gedcomText)
await loadData(importedData)
setGedcomStatus("GEDCOM 导入成功!")
if (gedcomInputRef.current) gedcomInputRef.current.value = ""
} catch (err) {
console.error(err)
setGedcomStatus("错误:无法解析 GEDCOM 文件")
}
}
reader.readAsText(file)
}}
/>
<Button variant="outline" onClick={() => gedcomInputRef.current?.click()}>
<Upload className="mr-2 h-4 w-4" />
GEDCOM
</Button>
{gedcomStatus && <span className="text-sm text-muted-foreground">{gedcomStatus}</span>}
</div>
</CardContent>
</Card>
</div>
{/* 数据库和系统管理 - 两列布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert>
<AlertDescription>
</AlertDescription>
</Alert>
<Button
variant="outline"
onClick={async () => {
if (confirm("确定要升级数据库吗?这会保留所有现有数据。")) {
try {
await upgradeDatabase()
alert("数据库升级成功!请刷新页面。")
window.location.reload()
} catch (error) {
alert("数据库升级失败:" + (error as Error).message)
}
}
}}
>
<Database className="mr-2 h-4 w-4" />
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-destructive" /> (Danger Zone)
</CardTitle>
<CardDescription>使</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="destructive"
onClick={async () => {
if (confirm("确定要重置吗?所有更改将丢失。")) {
await resetData()
alert("数据已重置为演示状态。")
}
}}
>
</Button>
</CardContent>
</Card>
</div>
{/* 操作日志 - 全宽 */}
<ErrorBoundary>
<ActivityLogViewer />
</ErrorBoundary>
</div>
</main>
</div>
)
}