Files
chinese-family-tree-2/app/members/[id]/page.tsx
T
freedakgmail 5c8c70097c 0.8.0.0
2025-12-21 17:32:33 +08:00

939 lines
44 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 { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from "@/components/ui/chinese-card"
import { ArrowLeft, Edit, Trash, Share2, Printer, CircleUser, CircleUserRound, Images, BookOpen, UserPlus, Camera, History } from "lucide-react"
import type { FamilyMember } from "@/types/family"
import { PhotoGallery } from "@/components/members/photo-gallery"
import { StoryManager } from "@/components/members/story-manager"
import { useDialog } from "@/components/ui/alert-dialog-custom"
// useDialog 已导入,用于 showAlert 和 showConfirm
import { permissions } from "@/lib/permissions"
import { MemberNameWithStatus } from "@/components/member-name-with-status"
import { MemberVersionHistory } from "@/components/members/member-version-history"
import { useSession } from "next-auth/react"
import { useEffect, useState, useCallback } from "react"
import { useParams, useRouter } from "next/navigation"
import Link from "next/link"
import { useFamily } from "@/context/family-context"
import { BackButton } from "@/components/ui/back-button"
import { SiteHeader } from "@/components/site-header"
import { MemberForm } from "@/components/members/member-form"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { AddRelationDialog, type RelationType } from "@/components/tree/add-relation-dialog"
export default function MemberProfilePage() {
const { id } = useParams()
const { treeData, currentTree, getMember, updateMember, deleteMember, isLoading } = useFamily()
const { showConfirm, showAlert } = useDialog()
const router = useRouter()
const { data: session, status } = useSession()
const [isEditing, setIsEditing] = useState(false)
const [editActiveTab, setEditActiveTab] = useState("info")
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | undefined>(undefined)
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
const [lastSaveTime, setLastSaveTime] = useState(Date.now())
// 添加成员对话框状态
const [relationDialog, setRelationDialog] = useState<{
type: RelationType
} | null>(null)
// 未登录时重定向到登录页
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/auth/signin')
}
}, [status, router])
const member = getMember(id as string)
const allMembers = Object.values(treeData.members)
// 判断是否已故
const isDead = !!member?.deathDate
// 根据在世/已故状态设置卡片样式
// 优先级:始祖 > 已故 > 在世
const cardStyle = member?.isFounder
? '!border-2 !border-amber-400 !bg-amber-50/30 dark:!bg-amber-900/20 shadow-md' // 始祖:金色边框(优先级最高)
: isDead
? '!border-2 !border-gray-400 dark:!border-gray-600 !bg-gray-50/50 dark:!bg-gray-900/30' // 已故:灰色深边框
: '!border-2 !border-amber-300/70 !bg-card dark:!border-amber-600/50' // 在世:浅金色边框
// 计算年龄或享年
const calculateAge = () => {
if (!member?.birthDate) return null
const birthYear = new Date(member.birthDate).getFullYear()
if (member.deathDate) {
// 已故,计算享年
const deathYear = new Date(member.deathDate).getFullYear()
return { age: deathYear - birthYear, type: 'deceased' }
} else {
// 在世,计算年龄
const currentYear = new Date().getFullYear()
return { age: currentYear - birthYear, type: 'living' }
}
}
const ageInfo = calculateAge()
// 获取用户角色
const role = currentTree?.currentUserRole
// Load avatar
useEffect(() => {
if (member?.avatarUrl) {
setAvatarBlobUrl(member.avatarUrl)
} else {
setAvatarBlobUrl(undefined)
}
}, [member?.avatarUrl])
// 姓名分解函数
const parseName = (fullName: string): { surname: string; givenName: string } => {
const name = fullName.trim()
if (!name) return { surname: '', givenName: '' }
const compoundSurnames = [
'欧阳', '太史', '端木', '上官', '司马', '东方', '独孤', '南宫', '万俟', '闻人',
'夏侯', '诸葛', '尉迟', '公羊', '赫连', '澹台', '皇甫', '宗政', '濮阳', '公冶',
'太叔', '申屠', '公孙', '慕容', '仲孙', '钟离', '长孙', '宇文', '司徒', '鲜于',
'司空', '闾丘', '子车', '亓官', '司寇', '巫马', '公西', '颛孙', '壤驷', '公良',
'漆雕', '乐正', '宰父', '谷梁', '拓跋', '夹谷', '轩辕', '令狐', '段干', '百里',
'呼延', '东郭', '南门', '羊舌', '微生', '公户', '公玉', '公仪', '梁丘', '公仲',
'公上', '公门', '公山', '公坚', '左丘', '公伯', '西门', '公祖', '第五', '公乘',
'贯丘', '公皙', '南荣', '东里', '东宫', '仲长', '子书', '子桑', '即墨', '达奚', '褚师'
]
for (const compound of compoundSurnames) {
if (name.startsWith(compound) && name.length > compound.length) {
return { surname: compound, givenName: name.slice(compound.length) }
}
}
if (name.length >= 2) {
return { surname: name.charAt(0), givenName: name.slice(1) }
}
return { surname: name, givenName: '' }
}
// 创建新成员的处理函数 - 必须在条件return之前定义
const handleCreateNewRelation = useCallback((prefillName: string, relationType: RelationType) => {
if (!member) return
const params = new URLSearchParams()
const { surname, givenName } = parseName(prefillName)
if (surname) params.set("surname", surname)
if (givenName) params.set("givenName", givenName)
if (relationType === "child") {
params.set("generation", String(member.generation + 1))
if (member.gender === "MALE") {
params.set("fatherId", member.id)
if (member.spouseIds?.length) {
params.set("motherId", member.spouseIds[0])
}
} else if (member.gender === "FEMALE") {
params.set("motherId", member.id)
if (member.spouseIds?.length) {
params.set("fatherId", member.spouseIds[0])
}
}
} else if (relationType === "spouse") {
params.set("generation", String(member.generation))
params.set("spouseId", member.id)
params.set("gender", member.gender === "MALE" ? "FEMALE" : "MALE")
if (member.childrenIds && member.childrenIds.length > 0) {
member.childrenIds.forEach(id => {
params.append('childrenIds', id)
})
}
} else if (relationType === "father") {
params.set("generation", String(member.generation === 1 ? 1 : member.generation - 1))
params.set("childId", member.id)
params.set("gender", "MALE")
if (member.motherId) {
params.set("spouseId", member.motherId)
}
} else if (relationType === "mother") {
params.set("generation", String(member.generation === 1 ? 1 : member.generation - 1))
params.set("childId", member.id)
params.set("gender", "FEMALE")
if (member.fatherId) {
params.set("spouseId", member.fatherId)
}
}
if (currentTree?.id) {
params.set("treeId", currentTree.id)
}
setRelationDialog(null)
router.push(`/members/new?${params.toString()}`)
}, [member, currentTree?.id, router])
// 显示加载状态
if (isLoading) {
return (
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<div className="flex-1 flex flex-col items-center justify-center gap-4">
<p className="text-muted-foreground">...</p>
</div>
</div>
)
}
// 成员未找到
if (!member) {
return (
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<div className="flex-1 flex flex-col items-center justify-center gap-4">
<p className="text-muted-foreground"></p>
<Button onClick={() => router.push(`/members${typeof window !== 'undefined' ? window.location.search : ''}`)}></Button>
</div>
</div>
)
}
const handleSave = async (updatedMember: FamilyMember) => {
await updateMember(updatedMember.id, updatedMember)
setIsEditing(false)
setLastSaveTime(Date.now())
}
const handleDelete = async () => {
// 检查是否正在删除始祖
let confirmMessage = "确定要删除这位成员吗?这将同时删除其关联关系。"
if (member.generation === 1 && (!member.fatherId && !member.motherId)) {
confirmMessage = "您正在删除始祖,这将导致全族代数重新计算。\n原第2代将变为第1代,以此类推。\n\n确定要删除这位成员吗?"
}
const confirmed = await showConfirm(confirmMessage, "删除成员", "destructive")
if (confirmed) {
try {
const result = await deleteMember(member.id)
// 构建跳转参数,只保留 treeId
const treeId = new URLSearchParams(window.location.search).get('treeId')
const params = new URLSearchParams()
if (treeId) params.set('treeId', treeId)
// 如果有提示信息(代数调整),显示给用户,等待确认后再跳转
if (result.message) {
await showAlert(result.message, "提示")
}
// 删除后跳转到成员列表页
router.push(`/members?${params.toString()}`)
} catch (error) {
console.error('删除成员失败:', error)
await showAlert('删除成员失败: ' + (error as Error).message, "错误")
}
}
}
// 创建新成员的处理函数 - 已移到条件return之前
const handleQuickAddChild = () => {
setRelationDialog({ type: 'child' })
}
const handleQuickAddSpouse = () => {
setRelationDialog({ type: 'spouse' })
}
const handleQuickAddFather = () => {
setRelationDialog({ type: 'father' })
}
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
try {
setIsUploadingAvatar(true)
// 上传到服务器
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || '上传失败')
}
const { url } = await response.json()
// 更新成员头像 URL
await updateMember(member.id, {
avatarUrl: url,
})
// 显示新头像
setAvatarBlobUrl(url)
setLastSaveTime(Date.now())
} catch (error) {
console.error('上传头像失败:', error)
showAlert('上传头像失败: ' + (error instanceof Error ? error.message : '未知错误'), "错误")
} finally {
setIsUploadingAvatar(false)
}
}
const handleQuickAddMother = () => {
setRelationDialog({ type: 'mother' })
}
if (isEditing) {
return (
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<main className="container mx-auto py-4 md:py-8 px-3 md:px-6">
<Tabs value={editActiveTab} onValueChange={setEditActiveTab} className="flex flex-col h-[calc(100vh-6rem)] md:h-[calc(100vh-8rem)]">
{/* 固定头部区域 */}
<div className="flex-shrink-0 pb-4 border-b border-border">
{/* 返回按钮 */}
<BackButton onClick={() => setIsEditing(false)} label="返回" />
{/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start gap-3 md:gap-6 pb-3 md:pb-4">
<div className="flex items-start gap-3 md:gap-6">
<div className="h-16 w-16 md:h-24 md:w-24 rounded-lg bg-muted flex items-center justify-center border-2 border-primary/20 overflow-hidden flex-shrink-0">
{member.avatarUrl ? (
<img
src={member.avatarUrl}
alt={member.fullName}
className="h-full w-full object-cover"
/>
) : (
member.gender === 'FEMALE' ? (
<CircleUserRound className="h-10 w-10 md:h-16 md:w-16 text-pink-400" />
) : (
<CircleUser className="h-10 w-10 md:h-16 md:w-16 text-blue-400" />
)
)}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 md:gap-3 mb-1 md:mb-2 flex-wrap">
<h1 className="text-xl md:text-3xl font-bold font-serif text-foreground flex items-center gap-1 md:gap-2">
{member.isFounder && <span className="text-amber-600 text-2xl">👑</span>}
<MemberNameWithStatus
name={member.fullName}
isDead={!!member.deathDate}
/>
</h1>
{member.isFounder ? (
<span className="px-2 md:px-3 py-0.5 md:py-1 bg-gradient-to-r from-amber-100 to-orange-100 text-amber-800 text-xs md:text-sm font-semibold rounded-full border-2 border-amber-300 shadow-sm">
· {member.generation}
</span>
) : (
<span className="px-1.5 md:px-2 py-0.5 md:py-1 bg-primary/10 text-primary text-xs md:text-sm font-medium rounded-full border border-primary/20">
{member.generation}
</span>
)}
</div>
<p className="text-sm md:text-base text-muted-foreground">
{member.isFounder
? '修改始祖信息将影响整个家族树'
: '更新成员信息'}
</p>
</div>
</div>
<div className="flex gap-2 flex-wrap">
<Button type="button" variant="outline" onClick={() => setIsEditing(false)}>
</Button>
<Button
type="button"
className="bg-primary text-primary-foreground hover:bg-primary/90"
onClick={() => {
const form = document.querySelector('form')
if (form) {
form.requestSubmit()
}
}}
>
</Button>
</div>
</div>
{/* Tab 导航栏 */}
<TabsList className="grid grid-cols-4 md:flex md:flex-wrap gap-1 md:gap-2 bg-muted/70 p-1 w-full md:w-fit h-auto">
<TabsTrigger value="info" className="text-xs md:text-sm px-2 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="details" className="text-xs md:text-sm px-2 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="photos" className="text-xs md:text-sm px-2 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="stories" className="text-xs md:text-sm px-2 md:px-3 py-1.5"></TabsTrigger>
</TabsList>
{/* 始祖特殊提示框 */}
{member.isFounder && (
<div className="mt-6 p-6 bg-gradient-to-br from-amber-50 via-orange-50 to-amber-50 border-2 border-amber-400 rounded-xl shadow-md">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-14 h-14 bg-gradient-to-br from-amber-200 to-orange-200 rounded-full flex items-center justify-center shadow-sm">
<span className="text-3xl">👑</span>
</div>
<div className="flex-1">
<h2 className="text-2xl font-serif font-bold text-amber-900 mb-3">
</h2>
<p className="text-base text-amber-800 leading-relaxed font-serif">
<span className="font-bold text-amber-900"></span>
<span className="font-bold text-amber-900"></span>
<br />
</p>
</div>
</div>
</div>
)}
</div>
{/* Tab 内容区域 - 可滚动 */}
<div className="flex-1 overflow-y-auto">
<MemberForm
initialData={member}
existingMembers={allMembers}
onSubmit={handleSave}
onCancel={() => setIsEditing(false)}
isFounder={member.isFounder}
hideTabs={true}
activeTab={editActiveTab}
/>
</div>
</Tabs>
</main>
</div>
)
}
return (
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<main className="container mx-auto py-4 md:py-8 px-3 md:px-6">
<Tabs defaultValue="info" className="flex flex-col h-[calc(100vh-6rem)] md:h-[calc(100vh-8rem)]">
{/* 固定头部区域 */}
<div className="flex-shrink-0 pb-3 md:pb-4 border-b border-border">
{/* 返回按钮 */}
<BackButton
onClick={() => router.push(`/members${typeof window !== 'undefined' ? window.location.search : ''}`)}
label="返回"
/>
{/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start gap-3 md:gap-6 pb-3 md:pb-4">
<div className="flex items-start gap-3 md:gap-6">
<div className="relative group flex-shrink-0">
<div className="h-16 w-16 md:h-24 md:w-24 rounded-lg bg-muted flex items-center justify-center border-2 border-primary/20 overflow-hidden">
{avatarBlobUrl ? (
<img
src={avatarBlobUrl}
alt={member.fullName}
className="h-full w-full object-cover"
/>
) : (
member.gender === 'FEMALE' ? (
<CircleUserRound className="h-10 w-10 md:h-16 md:w-16 text-pink-400" />
) : (
<CircleUser className="h-10 w-10 md:h-16 md:w-16 text-blue-400" />
)
)}
</div>
{permissions.canUpdate(role) && (
<label className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer rounded-lg">
<Camera className="h-5 w-5 md:h-6 md:w-6 text-white" />
<input
type="file"
accept="image/*"
className="hidden"
onChange={handleAvatarUpload}
disabled={isUploadingAvatar}
/>
</label>
)}
{isUploadingAvatar && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-lg">
<div className="text-white text-xs">...</div>
</div>
)}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 md:gap-3 mb-1 md:mb-2 flex-wrap">
<h1 className="text-xl md:text-3xl font-bold font-serif text-foreground flex items-center gap-1 md:gap-2">
{member.isFounder && <span className="text-amber-600 text-lg md:text-2xl">👑</span>}
<MemberNameWithStatus
name={member.fullName}
isDead={!!member.deathDate}
memberId={member.id}
treeId={currentTree?.id}
/>
</h1>
{member.isFounder ? (
<span className="px-2 md:px-3 py-0.5 md:py-1 bg-gradient-to-r from-amber-100 to-orange-100 text-amber-800 text-xs md:text-sm font-semibold rounded-full border-2 border-amber-300 shadow-sm">
· {member.generation}
</span>
) : (
<span className="px-1.5 md:px-2 py-0.5 md:py-1 bg-primary/10 text-primary text-xs md:text-sm font-medium rounded-full border border-primary/20">
{member.generation}
</span>
)}
</div>
<div className="flex flex-wrap gap-x-2 md:gap-x-4 gap-y-1 text-xs md:text-sm text-muted-foreground">
{member.courtesyName && <span>{member.courtesyName}</span>}
{member.artName && <span>{member.artName}</span>}
{member.generationName && <span>{member.generationName}</span>}
{ageInfo && <span>{ageInfo.type === 'deceased' ? '享年' : '年龄'}{ageInfo.age}</span>}
</div>
</div>
</div>
<div className="flex gap-1.5 md:gap-2 flex-wrap">
{permissions.canCreate(role) && (
<>
<Button variant="default" size="sm" onClick={handleQuickAddChild} className="gap-1 text-xs md:text-sm h-7 md:h-8 px-2 md:px-3">
<UserPlus className="h-3 w-3 md:h-4 md:w-4" /> <span className="hidden sm:inline"></span>
</Button>
<Button variant="secondary" size="sm" onClick={handleQuickAddSpouse} className="gap-1 text-xs md:text-sm h-7 md:h-8 px-2 md:px-3">
<UserPlus className="h-3 w-3 md:h-4 md:w-4" /> <span className="hidden sm:inline"></span>
</Button>
</>
)}
<Button variant="outline" size="icon" onClick={() => window.print()} title="打印" className="h-7 w-7 md:h-8 md:w-8">
<Printer className="h-3 w-3 md:h-4 md:w-4" />
</Button>
<Button variant="outline" size="icon" title="分享" className="h-7 w-7 md:h-8 md:w-8">
<Share2 className="h-3 w-3 md:h-4 md:w-4" />
</Button>
{permissions.canUpdate(role) && (
<Button variant="outline" size="sm" onClick={() => setIsEditing(true)} className="gap-1 text-xs md:text-sm h-7 md:h-8 px-2 md:px-3">
<Edit className="h-3 w-3 md:h-4 md:w-4" />
</Button>
)}
{permissions.canDeleteMember(role) && (
<Button variant="destructive" size="icon" onClick={handleDelete} title="删除" className="h-7 w-7 md:h-8 md:w-8">
<Trash className="h-3 w-3 md:h-4 md:w-4" />
</Button>
)}
</div>
</div>
{/* Tab 导航栏 */}
<TabsList className="grid grid-cols-5 md:flex md:flex-wrap gap-1 md:gap-2 bg-muted/70 p-1 w-full md:w-fit h-auto">
<TabsTrigger value="info" className="text-[11px] md:text-sm px-1.5 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="details" className="text-[11px] md:text-sm px-1.5 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="photos" className="text-[11px] md:text-sm px-1.5 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="stories" className="text-[11px] md:text-sm px-1.5 md:px-3 py-1.5"></TabsTrigger>
<TabsTrigger value="history" className="text-[11px] md:text-sm px-1.5 md:px-3 py-1.5">线</TabsTrigger>
</TabsList>
{/* 始祖特殊提示框 - 移动端简化 */}
{member.isFounder && (
<div className="mt-3 md:mt-6 p-3 md:p-6 bg-gradient-to-br from-amber-50 via-orange-50 to-amber-50 border-2 border-amber-400 rounded-xl shadow-md">
<div className="flex items-start gap-2 md:gap-4">
<div className="flex-shrink-0 w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-amber-200 to-orange-200 rounded-full flex items-center justify-center shadow-sm">
<span className="text-xl md:text-3xl">👑</span>
</div>
<div className="flex-1 min-w-0">
<h2 className="text-lg md:text-2xl font-serif font-bold text-amber-900 mb-1 md:mb-3">
</h2>
<p className="text-sm md:text-base text-amber-800 leading-relaxed font-serif">
<span className="font-bold text-amber-900"></span>
<span className="font-bold text-amber-900"></span>
<span className="hidden md:inline"><br /></span>
</p>
</div>
</div>
</div>
)}
</div>
{/* Tab 内容区域 - 可滚动 */}
<div className="flex-1 overflow-y-auto">
{/* 基本信息Tab - 包含基本信息和家庭关系 */}
<TabsContent value="info" className="mt-3 md:mt-6 space-y-4 md:space-y-6">
{/* 基本信息 */}
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-3 md:p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4"></ChineseCardTitle>
<dl className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-x-8 gap-y-4 text-sm">
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium text-lg">{member.fullName}</dd>
</div>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.gender === 'MALE' ? '男' : '女'}</dd>
</div>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium"> {member.generation} </dd>
</div>
{member.occupation && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.occupation}</dd>
</div>
)}
</dl>
</ChineseCardContent>
</ChineseCard>
{/* 家庭关系 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ChineseCard variant="default" cornerOrnament className={`rounded-lg ${cardStyle}`}>
<ChineseCardContent className="p-4">
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-3 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.fatherId ? (
<Link
href={`/members/${member.fatherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.fatherId)?.fullName || member.fatherId}
isDead={!!getMember(member.fatherId)?.deathDate}
/>
</Link>
) : member.spouseFatherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseFatherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.motherId ? (
<Link
href={`/members/${member.motherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.motherId)?.fullName || member.motherId}
isDead={!!getMember(member.motherId)?.deathDate}
/>
</Link>
) : member.spouseMotherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseMotherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
</div>
</ChineseCardContent>
</ChineseCard>
<ChineseCard variant="default" cornerOrnament className={`rounded-lg ${cardStyle}`}>
<ChineseCardContent className="p-4">
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.spouseIds?.length > 0 ? (
member.spouseIds.map((spouseId) => (
<Link
href={`/members/${spouseId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={spouseId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(spouseId)?.fullName || spouseId}
isDead={!!getMember(spouseId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.childrenIds?.length > 0 ? (
member.childrenIds.map((childId) => (
<Link
href={`/members/${childId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={childId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(childId)?.fullName || childId}
isDead={!!getMember(childId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
</div>
</ChineseCardContent>
</ChineseCard>
</div>
</TabsContent>
{/* 详细资料Tab */}
<TabsContent value="details" className="mt-6 space-y-6">
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4"></ChineseCardTitle>
<dl className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6 gap-x-8 gap-y-4 text-sm">
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium space-y-1">
<div className="flex flex-wrap items-center gap-2">
<span>{member.birthDate || "-"}</span>
{member.birthDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
<div className="flex flex-wrap items-center gap-2">
<span>{member.deathDate || "-"}</span>
{member.deathDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
</dd>
</div>
{ageInfo && (
<div>
<dt className="text-muted-foreground mb-1">{ageInfo.type === 'deceased' ? '享年' : '年龄'}</dt>
<dd className="font-medium text-primary">{ageInfo.age} </dd>
</div>
)}
{member.generationName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.generationName}</dd>
</div>
)}
{member.courtesyName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.courtesyName}</dd>
</div>
)}
{member.artName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.artName}</dd>
</div>
)}
{member.posthumousName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.posthumousName}</dd>
</div>
)}
{member.ancestralHome && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.ancestralHome}</dd>
</div>
)}
{member.birthPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.birthPlace}</dd>
</div>
)}
{member.deathPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.deathPlace}</dd>
</div>
)}
{member.burialPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.burialPlace}</dd>
</div>
)}
{member.address && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.address}</dd>
</div>
)}
{member.occupation && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.occupation}</dd>
</div>
)}
{member.phone && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`tel:${member.phone}`} className="hover:text-primary">{member.phone}</a>
</dd>
</div>
)}
{member.telephone && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`tel:${member.telephone}`} className="hover:text-primary">{member.telephone}</a>
</dd>
</div>
)}
{member.email && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`mailto:${member.email}`} className="hover:text-primary">{member.email}</a>
</dd>
</div>
)}
</dl>
</ChineseCardContent>
</ChineseCard>
{/* 个人简介 */}
{member.bio && (
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4"></ChineseCardTitle>
<p className="whitespace-pre-line leading-relaxed">{member.bio}</p>
</ChineseCardContent>
</ChineseCard>
)}
{/* 标签 */}
{member.tags && member.tags.length > 0 && (
<ChineseCard variant="default" cornerOrnament className={`shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4"></ChineseCardTitle>
<div className="flex flex-wrap gap-2">
{member.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-md border border-primary/20"
>
{tag}
</span>
))}
</div>
</ChineseCardContent>
</ChineseCard>
)}
</TabsContent>
{/* 生平影像Tab */}
<TabsContent value="photos" className="mt-6 space-y-6">
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4 flex items-center gap-2">
<Images className="h-5 w-5 text-primary" />
</ChineseCardTitle>
<PhotoGallery
photos={member.photos}
photoIds={member.photoIds}
onChange={async (photos) => {
await updateMember(member.id, { photos })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 家族故事Tab */}
<TabsContent value="stories" className="mt-6 space-y-6">
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4 flex items-center gap-2">
<BookOpen className="h-5 w-5 text-primary" />
</ChineseCardTitle>
{member.stories && member.stories.length > 0 ? (
<StoryManager
stories={member.stories}
onChange={async (stories) => {
await updateMember(member.id, { stories })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
) : (
<div className="text-sm text-muted-foreground text-center py-10">
</div>
)}
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 时间线Tab - 仅显示视图 */}
<TabsContent value="history" className="mt-6 space-y-6">
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-2"></ChineseCardTitle>
<p className="text-sm text-muted-foreground">
{new Date(lastSaveTime).toLocaleString('zh-CN')}
</p>
</ChineseCardContent>
</ChineseCard>
<ChineseCard variant="default" cornerOrnament className={`w-full shadow-sm ${cardStyle}`}>
<ChineseCardContent className="p-6">
<ChineseCardTitle className="text-lg font-serif font-semibold mb-4 flex items-center gap-2">
<History className="h-5 w-5 text-primary" />
</ChineseCardTitle>
<MemberVersionHistory
memberId={member.id}
treeId={currentTree?.id || ''}
lastUpdated={lastSaveTime}
/>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
</div>
</Tabs>
</main>
{/* 添加成员对话框 */}
{relationDialog && member && (
<AddRelationDialog
open={!!relationDialog}
onOpenChange={(open) => {
if (!open) setRelationDialog(null)
}}
member={member}
relationType={relationDialog.type}
onCreateNew={handleCreateNewRelation}
/>
)}
</div>
)
}