"use client" import type React from "react" import { createContext, useContext, useState, useCallback, useEffect, useRef, Suspense } from "react" import type { FamilyMember, FamilyTreeData } from "@/types/family" import { useSession } from "next-auth/react" import { useSearchParams } from "next/navigation" interface FamilyTreeInfo { id: string name: string description: string | null ownerId: string rootMemberId: string | null currentUserRole?: "OWNER" | "EDITOR" | "VIEWER" } interface FamilyContextType { treeData: FamilyTreeData currentTree: FamilyTreeInfo | null getMember: (id: string) => FamilyMember | undefined addMember: (member: FamilyMember) => Promise updateMember: (id: string, updates: Partial) => Promise deleteMember: (id: string) => Promise searchResults: FamilyMember[] searchMembers: (query: string) => void loadData: (data: FamilyTreeData) => Promise resetData: () => Promise isLoading: boolean highlightedMemberId: string | null setHighlightedMemberId: (id: string | null) => void } const FamilyContext = createContext(undefined) function FamilyProviderInner({ children }: { children: React.ReactNode }) { const { data: session, status } = useSession() const searchParams = useSearchParams() const [searchResults, setSearchResults] = useState([]) const [isLoading, setIsLoading] = useState(true) const [highlightedMemberId, setHighlightedMemberId] = useState(null) const [treeData, setTreeData] = useState({ rootId: "", members: {}, }) const [currentTree, setCurrentTree] = useState(null) const loadingTreeIdRef = useRef(null) // 监听 URL 和 Session 变化,加载家族树 useEffect(() => { const controller = new AbortController() const signal = controller.signal const loadTree = async () => { const treeId = searchParams.get('treeId') // 如果 session 正在加载,等待 if (status === 'loading') { return } // 如果 session 不存在(未登录),清除所有数据 if (!session?.user?.id) { if (!signal.aborted) { setCurrentTree(null) setTreeData({ rootId: "", members: {} }) setIsLoading(false) loadingTreeIdRef.current = null } return } // 需要加载的树 ID const targetTreeId = treeId || 'first' // 如果正在加载相同的树,跳过 if (loadingTreeIdRef.current === targetTreeId) { return } // 如果 treeId 与当前树相同,跳过 if (treeId && treeId === currentTree?.id) { return } // 标记正在加载 loadingTreeIdRef.current = targetTreeId if (!signal.aborted) setIsLoading(true) const fetchUrl = targetTreeId === 'first' ? '/api/trees' : `/api/trees/${targetTreeId}` try { const res = await fetch(fetchUrl, { signal }) if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`) } const data = await res.json() if (signal.aborted) return let tree = null if (targetTreeId === 'first') { tree = data.trees?.[0] } else { tree = data.tree } if (tree) { setCurrentTree(tree) // 加载该树的成员数据 try { const memberRes = await fetch(`/api/trees/${tree.id}/members`, { signal }) if (!memberRes.ok) { throw new Error(`Failed to load members: ${memberRes.status}`) } const memberData = await memberRes.json() if (signal.aborted) return if (memberData.members) { const membersMap = memberData.members.reduce( (acc: Record, member: FamilyMember) => { acc[member.id] = member return acc }, {} ) setTreeData({ rootId: memberData.rootId || "", members: membersMap, }) } } catch (err: any) { if (err.name === 'AbortError') { return } console.error('加载成员数据失败:', err) // 即使成员加载失败,也保留树的信息 setTreeData({ rootId: "", members: {} }) } } else { setCurrentTree(null) setTreeData({ rootId: "", members: {} }) } } catch (err: any) { if (err.name === 'AbortError') { return } // 忽略 Failed to fetch 错误 if (err.message === 'Failed to fetch' || err.toString().includes('Failed to fetch')) { return } console.error('加载家族树失败:', err.message || err) setCurrentTree(null) setTreeData({ rootId: "", members: {} }) } finally { if (!signal.aborted) { setIsLoading(false) // 加载完成后清除标记 loadingTreeIdRef.current = null } } } loadTree() return () => { controller.abort() } }, [searchParams, session?.user?.id, status]) const getMember = useCallback( (id: string) => { return treeData.members[id] }, [treeData], ) const addMember = useCallback(async (member: FamilyMember) => { if (!currentTree?.id) throw new Error('没有选择家族树') const response = await fetch(`/api/trees/${currentTree.id}/members`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(member), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || '添加成员失败') } const data = await response.json() // 更新本地状态 setTreeData(prev => ({ ...prev, members: { ...prev.members, [data.member.id]: data.member, }, })) }, [currentTree?.id]) const updateMember = useCallback(async (id: string, updates: Partial) => { if (!currentTree?.id) throw new Error('没有选择家族树') const response = await fetch(`/api/trees/${currentTree.id}/members/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || '更新成员失败') } const data = await response.json() // 更新本地状态 setTreeData(prev => ({ ...prev, members: { ...prev.members, [id]: data.member, }, })) }, [currentTree?.id]) const deleteMember = useCallback(async (id: string) => { if (!currentTree?.id) throw new Error('没有选择家族树') const response = await fetch(`/api/trees/${currentTree.id}/members/${id}`, { method: 'DELETE', }) if (!response.ok) { const error = await response.json() throw new Error(error.error || '删除成员失败') } // 更新本地状态 setTreeData(prev => { const newMembers = { ...prev.members } delete newMembers[id] return { ...prev, members: newMembers, } }) }, [currentTree?.id]) const loadData = useCallback(async (data: FamilyTreeData) => { if (!currentTree?.id) throw new Error('没有选择家族树') // 导入数据到服务器 const response = await fetch(`/api/trees/${currentTree.id}/import`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || '导入数据失败') } // 重新加载数据 const membersResponse = await fetch(`/api/trees/${currentTree.id}/members`) const membersData = await membersResponse.json() if (membersData.members) { const membersMap = membersData.members.reduce( (acc: Record, member: FamilyMember) => { acc[member.id] = member return acc }, {} ) setTreeData({ rootId: membersData.rootId || "", members: membersMap, }) } }, [currentTree?.id]) const resetData = useCallback(async () => { // 重置为空数据 setTreeData({ rootId: "", members: {} }) }, []) const searchMembers = useCallback( (query: string) => { if (!query.trim()) { setSearchResults([]) return } const lowerQuery = query.toLowerCase() // Perform search on the in-memory treeData for simplicity and speed // (Dexie also supports querying, but we already have the data loaded) const results = Object.values(treeData.members).filter( (member) => member.fullName.includes(lowerQuery) || member.givenName.includes(lowerQuery) || member.courtesyName?.includes(lowerQuery) || member.artName?.includes(lowerQuery), ) setSearchResults(results) }, [treeData], ) // Listen for import events useEffect(() => { const handleImport = (e: CustomEvent) => { loadData(e.detail) } window.addEventListener("family-data-import" as any, handleImport as any) return () => window.removeEventListener("family-data-import" as any, handleImport as any) }, [loadData]) // 监听 treeData 变化 // useEffect(() => { // console.log('🔄 FamilyContext treeData 更新:', { // rootId: treeData.rootId, // membersCount: Object.keys(treeData.members).length, // memberIds: Object.keys(treeData.members), // currentTreeId: currentTree?.id, // currentTreeName: currentTree?.name // }) // }, [treeData, currentTree]) return ( {children} ) } export function FamilyProvider({ children }: { children: React.ReactNode }) { return ( Loading...}> {children} ) } export function useFamily() { const context = useContext(FamilyContext) if (context === undefined) { throw new Error("useFamily must be used within a FamilyProvider") } return context }