This commit is contained in:
freedakgmail
2025-11-23 03:07:39 +08:00
parent 626d7dddde
commit e92884ae53
679 changed files with 208175 additions and 1183474 deletions
+201 -123
View File
@@ -3,14 +3,20 @@
import type React from "react"
import { createContext, useContext, useState, useCallback, useEffect } from "react"
import type { FamilyMember, FamilyTreeData } from "@/types/family"
import { mockFamilyData } from "@/lib/mock-data"
import { db } from "@/lib/db"
import { useLiveQuery } from "dexie-react-hooks"
import { setupDailyNotificationCheck } from "@/lib/notifications"
import { logActivity } from "@/lib/activity-logger"
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
}
interface FamilyContextType {
treeData: FamilyTreeData
currentTree: FamilyTreeInfo | null
getMember: (id: string) => FamilyMember | undefined
addMember: (member: FamilyMember) => Promise<void>
updateMember: (id: string, updates: Partial<FamilyMember>) => Promise<void>
@@ -27,51 +33,102 @@ interface FamilyContextType {
const FamilyContext = createContext<FamilyContextType | undefined>(undefined)
export function FamilyProvider({ children }: { children: React.ReactNode }) {
const { data: session } = useSession()
const searchParams = useSearchParams()
const [searchResults, setSearchResults] = useState<FamilyMember[]>([])
const [isInitialized, setIsInitialized] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [highlightedMemberId, setHighlightedMemberId] = useState<string | null>(null)
const [treeData, setTreeData] = useState<FamilyTreeData>({
rootId: "",
members: {},
})
const [currentTree, setCurrentTree] = useState<FamilyTreeInfo | null>(null)
// Live query to get all members
const members = useLiveQuery(() => db.members.toArray())
// Live query to get rootId
const rootIdSetting = useLiveQuery(() => db.settings.get("rootId"))
// Initialize DB with mock data if empty
// 监听 URL 和 Session 变化,加载家族树
useEffect(() => {
const initDb = async () => {
const count = await db.members.count()
if (count === 0) {
await db.transaction("rw", db.members, db.settings, async () => {
await db.members.bulkAdd(Object.values(mockFamilyData.members))
await db.settings.put({ key: "rootId", value: mockFamilyData.rootId })
})
}
setIsInitialized(true)
const treeId = searchParams.get('treeId')
console.log('FamilyContext - Effect triggered, treeId:', treeId, 'currentTree:', currentTree?.id)
// 如果 session 不存在,清除所有数据
if (!session?.user?.id) {
console.log('FamilyContext - No session, clearing data')
setCurrentTree(null)
setTreeData({ rootId: "", members: {} })
setIsLoading(false)
return
}
initDb()
}, [])
// 设置通知检查
useEffect(() => {
if (isInitialized && members && members.length > 0) {
// 检查通知权限并设置每日检查
if ("Notification" in window && Notification.permission === "granted") {
setupDailyNotificationCheck(members)
}
// 如果 treeId 与当前树相同,跳过
if (treeId && treeId === currentTree?.id) {
console.log('FamilyContext - Same tree, skipping')
return
}
}, [isInitialized, members])
// Construct treeData from DB results
const treeData: FamilyTreeData = {
rootId: rootIdSetting?.value || "",
members: (members || []).reduce(
(acc, member) => {
acc[member.id] = member
return acc
},
{} as Record<string, FamilyMember>,
),
}
// 如果没有 treeId 且已有树,跳过
if (!treeId && currentTree) {
console.log('FamilyContext - No treeId but has currentTree, skipping')
return
}
// 需要加载新树
const targetTreeId = treeId || 'first'
console.log('FamilyContext - Loading tree:', targetTreeId)
setIsLoading(true)
const fetchUrl = targetTreeId === 'first'
? '/api/trees'
: `/api/trees/${targetTreeId}`
console.log('FamilyContext - Fetching URL:', fetchUrl)
fetch(fetchUrl)
.then(res => res.json())
.then(data => {
let tree = null
if (targetTreeId === 'first') {
tree = data.trees?.[0]
} else {
tree = data.tree
}
console.log('FamilyContext - Fetched tree:', tree?.name, tree?.id)
if (tree) {
setCurrentTree(tree)
// 加载该树的成员数据
return fetch(`/api/trees/${tree.id}/members`)
.then(res => res.json())
.then(memberData => {
console.log('FamilyContext - Loaded members:', memberData.members?.length)
if (memberData.members) {
const membersMap = memberData.members.reduce(
(acc: Record<string, FamilyMember>, member: FamilyMember) => {
acc[member.id] = member
return acc
},
{}
)
setTreeData({
rootId: memberData.rootId || "",
members: membersMap,
})
}
})
} else {
// 没找到树(可能是被删除了,或者没有权限)
console.log('FamilyContext - Tree not found')
setCurrentTree(null)
setTreeData({ rootId: "", members: {} })
}
})
.catch(err => {
console.error('FamilyContext - Load failed:', err)
setCurrentTree(null)
setTreeData({ rootId: "", members: {} })
})
.finally(() => setIsLoading(false))
}, [searchParams, session?.user?.id])
const getMember = useCallback(
(id: string) => {
@@ -81,97 +138,118 @@ export function FamilyProvider({ children }: { children: React.ReactNode }) {
)
const addMember = useCallback(async (member: FamilyMember) => {
await db.transaction("rw", db.members, db.activityLogs, async () => {
// Add the member
await db.members.add(member)
// Update relationships (Father)
if (member.fatherId) {
const father = await db.members.get(member.fatherId)
if (father && !father.childrenIds.includes(member.id)) {
await db.members.update(member.fatherId, {
childrenIds: [...father.childrenIds, member.id],
})
}
}
// Update relationships (Mother)
if (member.motherId) {
const mother = await db.members.get(member.motherId)
if (mother && !mother.childrenIds.includes(member.id)) {
await db.members.update(member.motherId, {
childrenIds: [...mother.childrenIds, member.id],
})
}
}
// Update relationships (Spouses)
if (member.spouseIds && member.spouseIds.length > 0) {
for (const spouseId of member.spouseIds) {
const spouse = await db.members.get(spouseId)
if (spouse && !spouse.spouseIds.includes(member.id)) {
await db.members.update(spouseId, {
spouseIds: [...spouse.spouseIds, member.id],
})
}
}
}
// 记录日志(在事务内)
await logActivity("create", "member", member.id, member.fullName)
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<FamilyMember>) => {
await db.transaction("rw", db.members, db.activityLogs, async () => {
const member = await db.members.get(id)
await db.members.update(id, {
...updates,
updatedAt: new Date().toISOString(),
})
// 记录日志(在事务内)
if (member) {
await logActivity("update", "member", id, member.fullName, updates)
}
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) => {
await db.transaction("rw", db.members, db.settings, db.activityLogs, async () => {
const member = await db.members.get(id)
// Remove from DB
await db.members.delete(id)
// Note: In a real app, we should also remove references to this ID from other members'
// childrenIds, spouseIds, etc. For now, we'll keep it simple as per the original implementation.
// If we deleted the root, we might want to handle that, but for now let's leave it.
// 记录日志(在事务内)
if (member) {
await logActivity("delete", "member", id, member.fullName)
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) => {
await db.transaction("rw", db.members, db.settings, db.activityLogs, async () => {
await db.members.clear()
await db.members.bulkAdd(Object.values(data.members))
await db.settings.put({ key: "rootId", value: data.rootId })
// 记录日志(在事务内)
await logActivity("import", "settings", undefined, "导入家族数据", {
memberCount: Object.keys(data.members).length
})
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<string, FamilyMember>, member: FamilyMember) => {
acc[member.id] = member
return acc
},
{}
)
setTreeData({
rootId: membersData.rootId || "",
members: membersMap,
})
}
}, [currentTree?.id])
const resetData = useCallback(async () => {
await loadData(mockFamilyData)
}, [loadData])
// 重置为空数据
setTreeData({ rootId: "", members: {} })
}, [])
const searchMembers = useCallback(
(query: string) => {
@@ -195,8 +273,7 @@ export function FamilyProvider({ children }: { children: React.ReactNode }) {
[treeData],
)
// Listen for import events (legacy support for the file input in settings if needed,
// but we should prefer using the context method)
// Listen for import events
useEffect(() => {
const handleImport = (e: CustomEvent<FamilyTreeData>) => {
loadData(e.detail)
@@ -209,6 +286,7 @@ export function FamilyProvider({ children }: { children: React.ReactNode }) {
<FamilyContext.Provider
value={{
treeData,
currentTree,
getMember,
addMember,
updateMember,
@@ -217,7 +295,7 @@ export function FamilyProvider({ children }: { children: React.ReactNode }) {
searchMembers,
loadData,
resetData,
isLoading: !isInitialized || !members,
isLoading,
highlightedMemberId,
setHighlightedMemberId,
}}