Files
freedakgmail 5c8c70097c 0.8.0.0
2025-12-21 17:32:33 +08:00

288 lines
11 KiB
TypeScript
Raw Permalink 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 { useMemo, useState, useEffect } from "react"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { ScrollArea } from "@/components/ui/scroll-area"
import { MemberNameWithStatus } from "@/components/member-name-with-status"
import { useFamily } from "@/context/family-context"
import { useToast } from "@/components/ui/use-toast"
import type { FamilyMember } from "@/types/family"
import { GenerationWarningDialog } from "@/components/tree/generation-warning-dialog"
import { shouldShowGenerationWarning } from "@/lib/generation-utils"
export type RelationType = "child" | "spouse" | "father" | "mother"
interface AddRelationDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
member: FamilyMember
relationType: RelationType
onCreateNew: (prefillName: string, relationType: RelationType) => void
}
interface CandidateMeta {
member: FamilyMember
fatherName?: string
motherName?: string
}
export function AddRelationDialog({
open,
onOpenChange,
member,
relationType,
onCreateNew,
}: AddRelationDialogProps) {
const { treeData, updateMember } = useFamily()
const { toast } = useToast()
const [query, setQuery] = useState("")
const [linkingId, setLinkingId] = useState<string | null>(null)
// 代数重新计算警告对话框状态
const [showWarningDialog, setShowWarningDialog] = useState(false)
const [pendingAction, setPendingAction] = useState<{ type: 'link' | 'create', target?: FamilyMember } | null>(null)
useEffect(() => {
if (!open) {
setQuery("")
setLinkingId(null)
setShowWarningDialog(false)
setPendingAction(null)
}
}, [open])
const normalizedQuery = query.trim().toLowerCase()
const candidates: CandidateMeta[] = useMemo(() => {
if (!normalizedQuery) return []
const members = Object.values(treeData.members)
return members
.filter((candidate) => {
if (candidate.id === member.id) return false
if (!candidate.fullName.toLowerCase().includes(normalizedQuery) &&
!(candidate.givenName || "").toLowerCase().includes(normalizedQuery) &&
!(candidate.courtesyName || "").toLowerCase().includes(normalizedQuery)) {
return false
}
if (relationType === "child") {
if (candidate.generation !== (member.generation + 1)) return false
if (member.childrenIds?.includes(candidate.id)) return false
if (member.spouseIds?.includes(candidate.id)) return false
const fatherMatches = candidate.fatherId ? candidate.fatherId === member.id : true
const motherMatches = candidate.motherId ? candidate.motherId === member.id : true
if (member.gender === "MALE" && !fatherMatches) return false
if (member.gender === "FEMALE" && !motherMatches) return false
if (member.gender !== "MALE" && member.gender !== "FEMALE") {
if (candidate.fatherId && candidate.motherId) return false
}
} else if (relationType === "spouse") {
if (candidate.generation !== member.generation) return false
if (member.spouseIds?.includes(candidate.id)) return false
if (candidate.spouseIds?.includes(member.id)) return false
if (candidate.gender === member.gender) return false
} else if (relationType === "father") {
if (candidate.gender !== "MALE") return false
if (candidate.generation !== (member.generation - 1)) return false
if (member.fatherId === candidate.id) return false
} else if (relationType === "mother") {
if (candidate.gender !== "FEMALE") return false
if (candidate.generation !== (member.generation - 1)) return false
if (member.motherId === candidate.id) return false
}
return true
})
.slice(0, 8)
.map((candidate) => ({
member: candidate,
fatherName: candidate.fatherId ? treeData.members[candidate.fatherId]?.fullName : undefined,
motherName: candidate.motherId ? treeData.members[candidate.motherId]?.fullName : undefined,
}))
}, [member, relationType, treeData.members, normalizedQuery])
const handleLink = async (target: FamilyMember) => {
// 检查是否需要显示代数重新计算警告
if ((relationType === 'father' || relationType === 'mother') && shouldShowGenerationWarning(member, relationType)) {
setPendingAction({ type: 'link', target })
setShowWarningDialog(true)
return
}
await performLink(target)
}
const performLink = async (target: FamilyMember) => {
try {
setLinkingId(target.id)
if (relationType === "child") {
const payload: Partial<FamilyMember> = {}
if (member.gender === "FEMALE") {
payload.motherId = member.id
} else if (member.gender === "MALE") {
payload.fatherId = member.id
} else {
if (!target.fatherId) {
payload.fatherId = member.id
} else if (!target.motherId) {
payload.motherId = member.id
} else {
throw new Error("该成员已绑定父母")
}
}
await updateMember(target.id, payload)
toast({
title: "关联成功",
description: `已将 ${target.fullName} 标记为 ${member.fullName} 的子女。`
})
} else if (relationType === "spouse") {
const existing = new Set(member.spouseIds || [])
existing.add(target.id)
await updateMember(member.id, { spouseIds: Array.from(existing) })
toast({
title: "关联成功",
description: `${member.fullName}${target.fullName} 已建立配偶关系。`
})
} else if (relationType === "father") {
await updateMember(member.id, { fatherId: target.id })
toast({
title: "关联成功",
description: `已将 ${target.fullName} 设为 ${member.fullName} 的父亲。`
})
} else if (relationType === "mother") {
await updateMember(member.id, { motherId: target.id })
toast({
title: "关联成功",
description: `已将 ${target.fullName} 设为 ${member.fullName} 的母亲。`
})
}
onOpenChange(false)
} catch (error) {
const message = error instanceof Error ? error.message : "关联失败"
toast({
title: "操作失败",
description: message,
variant: "destructive",
})
} finally {
setLinkingId(null)
}
}
const handleCreateNew = () => {
if (!normalizedQuery) return
// 检查是否需要显示代数重新计算警告
if ((relationType === 'father' || relationType === 'mother') && shouldShowGenerationWarning(member, relationType)) {
setPendingAction({ type: 'create' })
setShowWarningDialog(true)
return
}
onCreateNew(query.trim(), relationType)
}
// 确认添加父母后的处理
const handleConfirmAddParent = () => {
if (pendingAction?.type === 'link' && pendingAction.target) {
performLink(pendingAction.target)
} else if (pendingAction?.type === 'create') {
onCreateNew(query.trim(), relationType)
}
setPendingAction(null)
}
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>{relationType === "child" ? "子女" : relationType === "spouse" ? "配偶" : relationType === "father" ? "父亲" : "母亲"}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="relation-name"></Label>
<Input
id="relation-name"
placeholder="请输入姓名或字号"
value={query}
autoFocus
onChange={(e) => setQuery(e.target.value)}
/>
</div>
{normalizedQuery ? (
candidates.length > 0 ? (
<div>
<p className="text-sm text-muted-foreground mb-2"></p>
<ScrollArea className="max-h-64 rounded-md border">
<div className="divide-y">
{candidates.map(({ member: candidate, fatherName, motherName }) => (
<div key={candidate.id} className="p-3 flex flex-col gap-2">
<div className="flex items-center justify-between gap-3">
<div>
<MemberNameWithStatus name={candidate.fullName} isDead={!!candidate.deathDate} />
<p className="text-xs text-muted-foreground">
{candidate.generation} · {candidate.gender === "FEMALE" ? "女" : "男"}
</p>
</div>
<Button
size="sm"
onClick={() => handleLink(candidate)}
disabled={linkingId === candidate.id}
>
{linkingId === candidate.id ? "关联中..." : "关联"}
</Button>
</div>
<div className="text-xs text-muted-foreground">
<div>{fatherName || "未登记"}</div>
<div>{motherName || "未登记"}</div>
</div>
</div>
))}
</div>
</ScrollArea>
</div>
) : (
<p className="text-sm text-muted-foreground"></p>
)
) : (
<p className="text-sm text-muted-foreground"></p>
)}
</div>
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-between">
<Button variant="outline" onClick={() => onOpenChange(false)} className="sm:flex-1">
</Button>
<Button onClick={handleCreateNew} disabled={!normalizedQuery} className="sm:flex-1">
"{query || ""}"
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 代数重新计算警告对话框 */}
{pendingAction && (relationType === 'father' || relationType === 'mother') && (
<GenerationWarningDialog
open={showWarningDialog}
onOpenChange={setShowWarningDialog}
memberName={member.fullName}
relationType={relationType}
onConfirm={handleConfirmAddParent}
/>
)}
</>
)
}