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

312 lines
12 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 { useRouter, useSearchParams } from "next/navigation"
import { useFamily } from "@/context/family-context"
import { SiteHeader } from "@/components/site-header"
import { MemberForm } from "@/components/members/member-form"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { UserPlus } from "lucide-react"
import type { FamilyMember } from "@/types/family"
import { useMemo, useEffect, useState } from "react"
import { useSession } from "next-auth/react"
import { BackButton } from "@/components/ui/back-button"
import { useDialog } from "@/components/ui/alert-dialog-custom"
export default function NewMemberPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { addMember, treeData, getMember } = useFamily()
const { showAlert, showConfirm } = useDialog()
const { data: session, status } = useSession()
const [activeTab, setActiveTab] = useState("info")
const existingMembers = Object.values(treeData.members)
const isFirstMember = existingMembers.length === 0
// 未登录时重定向到登录页
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/auth/signin')
}
}, [status, router])
// 从URL参数获取预填信息
const initialData = useMemo(() => {
const fatherId = searchParams.get('fatherId')
const motherId = searchParams.get('motherId')
const spouseId = searchParams.get('spouseId')
const childId = searchParams.get('childId')
const generation = searchParams.get('generation')
const gender = searchParams.get('gender')
const surname = searchParams.get('surname')
const givenName = searchParams.get('givenName')
if (!fatherId && !motherId && !spouseId && !childId && !generation && !gender && !surname && !givenName) return undefined
const data: Partial<FamilyMember> = {}
// 处理姓名信息
if (surname) {
data.surname = surname
}
if (givenName) {
data.givenName = givenName
}
// 如果有姓和名,自动生成全名
if (surname || givenName) {
data.fullName = (surname || '') + (givenName || '')
}
// 处理父母信息
if (fatherId) {
data.fatherId = fatherId
}
if (motherId) {
data.motherId = motherId
}
// 如果只有父亲没有母亲,尝试自动获取母亲(父亲的配偶)
if (fatherId && !motherId) {
const father = getMember(fatherId)
console.log('Looking for mother (father\'s spouse):', { fatherId, father, spouseIds: father?.spouseIds })
if (father?.spouseIds && father.spouseIds.length > 0) {
data.motherId = father.spouseIds[0]
console.log('Found mother:', data.motherId)
}
}
// 如果只有母亲没有父亲,尝试自动获取父亲(母亲的配偶)
if (motherId && !fatherId) {
const mother = getMember(motherId)
console.log('Looking for father (mother\'s spouse):', { motherId, mother, spouseIds: mother?.spouseIds })
if (mother?.spouseIds && mother.spouseIds.length > 0) {
data.fatherId = mother.spouseIds[0]
console.log('Found father:', data.fatherId)
}
}
// 处理配偶信息
if (spouseId) {
data.spouseIds = [spouseId]
}
// 处理子女信息
const childrenIds = searchParams.getAll('childrenIds')
if (childrenIds.length > 0) {
data.childrenIds = childrenIds
} else if (childId) {
data.childrenIds = [childId]
}
// 处理世代
if (generation) {
data.generation = parseInt(generation)
}
// 处理性别
if (gender) {
data.gender = gender.toUpperCase() as 'MALE' | 'FEMALE'
}
return data
}, [searchParams, getMember])
// 检测添加成员的类型
const memberType = useMemo(() => {
const fatherId = searchParams.get('fatherId')
const motherId = searchParams.get('motherId')
const spouseId = searchParams.get('spouseId')
const childId = searchParams.get('childId')
const childrenIds = searchParams.getAll('childrenIds')
if (isFirstMember) return '始祖'
if (fatherId || motherId) return '子女'
if (spouseId) return '配偶'
if (childId || childrenIds.length > 0) return '父母'
return '成员'
}, [searchParams, isFirstMember])
// 判断是否是给始祖添加父母(新成员将成为新始祖)
const isAddingParentToFounder = useMemo(() => {
const childId = searchParams.get('childId')
if (!childId) return false
const child = getMember(childId)
return child?.generation === 1
}, [searchParams, getMember])
// 是否应该显示始祖样式
const showFounderStyle = isFirstMember || isAddingParentToFounder
const handleSave = async (newMember: FamilyMember) => {
try {
// 检查是否正在给始祖添加父母
const childId = searchParams.get('childId')
if (childId && memberType === '父母') {
const child = getMember(childId)
// 如果子女不存在(已被删除),提示用户
if (!child) {
await showAlert('目标成员已不存在,无法添加父母。请返回成员列表重新操作。', "提示")
const params = new URLSearchParams(window.location.search)
router.push(`/members?${params.toString()}`)
return
}
// 如果子女是第1代(始祖),需要确认
if (child.generation === 1) {
const confirmed = await showConfirm(
'您正在给始祖添加父母,这将导致全族代数重新计算。\n原第1代将变为第2代,以此类推。\n\n是否继续?',
"确认操作"
)
if (!confirmed) {
return
}
}
}
// 如果是第一个成员,自动设置为始祖
const memberData = isFirstMember ? {
...newMember,
generation: 1,
isFounder: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} : {
...newMember,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
const result = await addMember(memberData)
// 构建跳转参数
const params = new URLSearchParams()
const treeId = searchParams.get('treeId')
if (treeId) params.set('treeId', treeId)
// 如果有提示信息(代数调整),显示给用户,等待用户确认后再跳转
if (result.message) {
await showAlert(result.message, "提示")
}
// 跳转到新成员的详情页
router.push(`/members/${result.member.id}?${params.toString()}`)
} catch (error) {
console.error('添加成员失败:', error)
await showAlert('添加成员失败: ' + (error as Error).message, "错误")
}
}
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={activeTab} onValueChange={setActiveTab} 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.back()} />
{/* 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-14 w-14 md:h-24 md:w-24 rounded-lg bg-muted flex items-center justify-center border-2 ${showFounderStyle ? 'border-amber-400 bg-amber-50/30' : 'border-primary/20'} overflow-hidden flex-shrink-0`}>
{showFounderStyle ? (
<span className="text-2xl md:text-4xl">👑</span>
) : (
<UserPlus className="h-8 w-8 md:h-16 md:w-16 text-muted-foreground" />
)}
</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-lg md:text-3xl font-bold font-serif text-foreground flex items-center gap-1 md:gap-2">
{showFounderStyle && <span className="text-amber-600 text-lg md:text-2xl">👑</span>}
{isFirstMember ? '添加家族始祖' : isAddingParentToFounder ? '添加新始祖' : `添加${memberType}`}
</h1>
{showFounderStyle && (
<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">
· 1
</span>
)}
</div>
<p className="text-xs md:text-base text-muted-foreground">
{isFirstMember
? '请认真填写始祖信息'
: `填写${memberType}信息`}
</p>
</div>
</div>
<div className="flex gap-1.5 md:gap-2 flex-wrap">
<Button type="button" variant="outline" onClick={() => router.back()} className="text-xs md:text-sm h-7 md:h-9 px-2 md:px-4">
</Button>
<Button
type="button"
className="bg-primary text-primary-foreground hover:bg-primary/90 text-xs md:text-sm h-7 md:h-9 px-2 md:px-4"
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>
{/* 始祖特殊提示框 - 移动端简化 */}
{isFirstMember && (
<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">
<MemberForm
initialData={initialData}
existingMembers={existingMembers}
onSubmit={handleSave}
onCancel={() => router.back()}
isFounder={isFirstMember}
hideTabs={true}
activeTab={activeTab}
/>
</div>
</Tabs>
</main>
</div>
)
}