197 lines
6.1 KiB
TypeScript
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
|
|
}
|