Files
chinese-family-tree-2/context/family-context.tsx
T
freedakgmail 43c8389705 0.0.0.1
2025-11-22 19:52:55 +08:00

197 lines
6.1 KiB
TypeScript

"use client"
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"
interface FamilyContextType {
treeData: FamilyTreeData
getMember: (id: string) => FamilyMember | undefined
addMember: (member: FamilyMember) => Promise<void>
updateMember: (id: string, updates: Partial<FamilyMember>) => Promise<void>
deleteMember: (id: string) => Promise<void>
searchResults: FamilyMember[]
searchMembers: (query: string) => void
loadData: (data: FamilyTreeData) => Promise<void>
resetData: () => Promise<void>
isLoading: boolean
}
const FamilyContext = createContext<FamilyContextType | undefined>(undefined)
export function FamilyProvider({ children }: { children: React.ReactNode }) {
const [searchResults, setSearchResults] = useState<FamilyMember[]>([])
const [isInitialized, setIsInitialized] = useState(false)
// 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
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)
}
initDb()
}, [])
// 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>,
),
}
const getMember = useCallback(
(id: string) => {
return treeData.members[id]
},
[treeData],
)
const addMember = useCallback(async (member: FamilyMember) => {
await db.transaction("rw", db.members, 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],
})
}
}
}
})
}, [])
const updateMember = useCallback(async (id: string, updates: Partial<FamilyMember>) => {
await db.members.update(id, {
...updates,
updatedAt: new Date().toISOString(),
})
}, [])
const deleteMember = useCallback(async (id: string) => {
await db.transaction("rw", db.members, db.settings, async () => {
// 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.
})
}, [])
const loadData = useCallback(async (data: FamilyTreeData) => {
await db.transaction("rw", db.members, db.settings, async () => {
await db.members.clear()
await db.members.bulkAdd(Object.values(data.members))
await db.settings.put({ key: "rootId", value: data.rootId })
})
}, [])
const resetData = useCallback(async () => {
await loadData(mockFamilyData)
}, [loadData])
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 (legacy support for the file input in settings if needed,
// but we should prefer using the context method)
useEffect(() => {
const handleImport = (e: CustomEvent<FamilyTreeData>) => {
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])
return (
<FamilyContext.Provider
value={{
treeData,
getMember,
addMember,
updateMember,
deleteMember,
searchResults,
searchMembers,
loadData,
resetData,
isLoading: !isInitialized || !members,
}}
>
{children}
</FamilyContext.Provider>
)
}
export function useFamily() {
const context = useContext(FamilyContext)
if (context === undefined) {
throw new Error("useFamily must be used within a FamilyProvider")
}
return context
}