Files
chinese-family-tree-2/app/relationship/page.tsx
T
freedakgmail 8b32cda811 0.0.5.0
2025-11-23 14:58:51 +08:00

403 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { useState } from 'react'
import { SiteHeader } from '@/components/site-header'
import { useFamily } from '@/context/family-context'
import { useRelationship } from '@/hooks/use-relationship'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
import { Check, ChevronsUpDown } from 'lucide-react'
import { cn } from '@/lib/utils'
export default function RelationshipTestPage() {
const { treeData } = useFamily()
const { calculateRelationship, getDirectRelatives, getSiblings } = useRelationship()
const [fromId, setFromId] = useState<string>('')
const [toId, setToId] = useState<string>('')
const [result, setResult] = useState<any>(null)
const [openFromCombobox, setOpenFromCombobox] = useState(false)
const [openToCombobox, setOpenToCombobox] = useState(false)
const members = Object.values(treeData.members)
const handleCalculate = () => {
if (!fromId || !toId) return
const relationship = calculateRelationship(fromId, toId)
const fromMember = treeData.members[fromId]
const toMember = treeData.members[toId]
setResult({
from: fromMember,
to: toMember,
relationship
})
}
return (
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<main className="flex-1 container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-6">
{/* 标题 */}
<div>
<h1 className="text-3xl font-bold font-serif mb-2"></h1>
<p className="text-muted-foreground">
</p>
</div>
{/* 选择成员 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">A</label>
<Popover open={openFromCombobox} onOpenChange={setOpenFromCombobox}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openFromCombobox}
className="w-full justify-between"
>
{fromId
? members.find((member) => member.id === fromId)?.fullName
: "搜索或选择成员A..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="输入姓名搜索..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
{members.map((member) => (
<CommandItem
key={member.id}
value={`${member.fullName} ${member.generation}`}
onSelect={() => {
setFromId(member.id)
setOpenFromCombobox(false)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
fromId === member.id ? "opacity-100" : "opacity-0"
)}
/>
{member.fullName} ({member.generation})
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">B</label>
<Popover open={openToCombobox} onOpenChange={setOpenToCombobox}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openToCombobox}
className="w-full justify-between"
>
{toId
? members.find((member) => member.id === toId)?.fullName
: "搜索或选择成员B..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="输入姓名搜索..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
{members.map((member) => (
<CommandItem
key={member.id}
value={`${member.fullName} ${member.generation}`}
onSelect={() => {
setToId(member.id)
setOpenToCombobox(false)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
toId === member.id ? "opacity-100" : "opacity-0"
)}
/>
{member.fullName} ({member.generation})
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
<Button
onClick={handleCalculate}
disabled={!fromId || !toId}
className="w-full"
>
</Button>
</CardContent>
</Card>
{/* 计算结果 */}
{result && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-center gap-4 p-6 bg-muted rounded-lg">
<div className="text-center">
<div className="text-2xl font-bold font-serif mb-1">
{result.from.fullName}
</div>
<div className="text-sm text-muted-foreground">
{result.from.generation}
</div>
</div>
<div className="flex flex-col items-center gap-2">
<Badge variant="default" className="text-lg px-4 py-2">
{result.relationship.term}
</Badge>
{result.relationship.path && (
<span className="text-xs text-muted-foreground">
: {result.relationship.path}
</span>
)}
</div>
<div className="text-center">
<div className="text-2xl font-bold font-serif mb-1">
{result.to.fullName}
</div>
<div className="text-sm text-muted-foreground">
{result.to.generation}
</div>
</div>
</div>
{result.relationship.description && (
<div className="p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div className="text-sm font-medium mb-1"></div>
<div className="text-muted-foreground">
<strong>{result.from.fullName}</strong> <strong>{result.to.fullName}</strong> <strong className="text-primary">{result.relationship.term}</strong>
</div>
</div>
)}
{result.relationship.success === false && (
<div className="p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
<div className="text-sm text-yellow-800 dark:text-yellow-200">
{result.relationship.error || '无法计算关系'}
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* 直系亲属信息 */}
{(fromId || toId) && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{fromId && (
<Card>
<CardHeader>
<CardTitle>
{treeData.members[fromId]?.fullName}
</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const relatives = getDirectRelatives(fromId)
if (!relatives) return null
return (
<>
<div className="grid grid-cols-1 gap-3">
{relatives.father && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
<div className="font-medium">{relatives.father.fullName}</div>
</div>
)}
{relatives.mother && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
<div className="font-medium">{relatives.mother.fullName}</div>
</div>
)}
{relatives.spouses.length > 0 && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
{relatives.spouses.map((spouse: any) => (
<div key={spouse.id} className="font-medium">
{spouse.fullName}
</div>
))}
</div>
)}
{relatives.children.length > 0 && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1">
({relatives.children.length})
</div>
<div className="space-y-1">
{relatives.children.slice(0, 3).map((child: any) => (
<div key={child.id} className="font-medium text-sm">
{child.fullName}
</div>
))}
{relatives.children.length > 3 && (
<div className="text-xs text-muted-foreground">
{relatives.children.length - 3} ...
</div>
)}
</div>
</div>
)}
</div>
{(() => {
const siblings = getSiblings(fromId)
if (siblings.length === 0) return null
return (
<div className="mt-3 p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-2">
({siblings.length})
</div>
<div className="flex flex-wrap gap-2">
{siblings.map((sibling: any) => (
<Badge key={sibling.id} variant="outline">
{sibling.fullName}
</Badge>
))}
</div>
</div>
)
})()}
</>
)
})()}
</CardContent>
</Card>
)}
{toId && (
<Card>
<CardHeader>
<CardTitle>
{treeData.members[toId]?.fullName}
</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const relatives = getDirectRelatives(toId)
if (!relatives) return null
return (
<>
<div className="grid grid-cols-1 gap-3">
{relatives.father && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
<div className="font-medium">{relatives.father.fullName}</div>
</div>
)}
{relatives.mother && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
<div className="font-medium">{relatives.mother.fullName}</div>
</div>
)}
{relatives.spouses.length > 0 && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1"></div>
{relatives.spouses.map((spouse: any) => (
<div key={spouse.id} className="font-medium">
{spouse.fullName}
</div>
))}
</div>
)}
{relatives.children.length > 0 && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-1">
({relatives.children.length})
</div>
<div className="space-y-1">
{relatives.children.slice(0, 3).map((child: any) => (
<div key={child.id} className="font-medium text-sm">
{child.fullName}
</div>
))}
{relatives.children.length > 3 && (
<div className="text-xs text-muted-foreground">
{relatives.children.length - 3} ...
</div>
)}
</div>
</div>
)}
</div>
{(() => {
const siblings = getSiblings(toId)
if (siblings.length === 0) return null
return (
<div className="mt-3 p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground mb-2">
({siblings.length})
</div>
<div className="flex flex-wrap gap-2">
{siblings.map((sibling: any) => (
<Badge key={sibling.id} variant="outline">
{sibling.fullName}
</Badge>
))}
</div>
</div>
)
})()}
</>
)
})()}
</CardContent>
</Card>
)}
</div>
)}
</div>
</main>
</div>
)
}