Files
chinese-family-tree-2/app/timeline/page.tsx
T
freedakgmail 33cf0c50d1 0.0.0.2
2025-11-22 20:08:07 +08:00

196 lines
8.7 KiB
TypeScript

"use client"
import { SiteHeader } from "@/components/site-header"
import { useFamily } from "@/context/family-context"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Clock, Map, ArrowRight } from "lucide-react"
export default function TimelinePage() {
const { treeData, getMember } = useFamily()
const members = Object.values(treeData.members)
// -- Timeline Logic --
// Flatten events: Births and Deaths
const events = members
.flatMap((m) => {
const evs = []
if (m.birthDate) {
evs.push({
date: m.birthDate,
year: Number.parseInt(m.birthDate.substring(0, 4)),
type: "birth",
member: m,
})
}
if (m.deathDate) {
evs.push({
date: m.deathDate,
year: Number.parseInt(m.deathDate.substring(0, 4)),
type: "death",
member: m,
})
}
return evs
})
.sort((a, b) => a.date.localeCompare(b.date))
// -- Migration Logic --
// Find moves between generations (Father -> Child location change)
const migrations = members
.flatMap((m) => {
if (!m.fatherId) return []
const father = getMember(m.fatherId)
if (!father) return []
// Check if location exists and is different
const loc1 = father.ancestralHome || father.birthPlace
const loc2 = m.ancestralHome || m.birthPlace
if (loc1 && loc2 && loc1 !== loc2) {
return [
{
year: m.birthDate ? Number.parseInt(m.birthDate.substring(0, 4)) : null,
from: loc1,
to: loc2,
person: m,
father: father,
},
]
}
return []
})
.sort((a, b) => (a.year || 0) - (b.year || 0))
return (
<div className="min-h-screen bg-background flex flex-col font-sans">
<SiteHeader />
<main className="container mx-auto py-8 px-4 md:px-6 flex-1">
<div className="mb-8">
<h1 className="text-3xl font-serif font-bold mb-2"> (History)</h1>
<p className="text-muted-foreground"></p>
</div>
<Tabs defaultValue="timeline" className="w-full">
<TabsList className="mb-6 w-full md:w-auto">
<TabsTrigger value="timeline" className="flex items-center gap-2">
<Clock className="h-4 w-4" /> (Chronology)
</TabsTrigger>
<TabsTrigger value="migration" className="flex items-center gap-2">
<Map className="h-4 w-4" /> (Migration)
</TabsTrigger>
</TabsList>
<TabsContent value="timeline">
<Card className="border-none shadow-none bg-transparent">
<CardContent className="p-0">
<div className="relative border-l-2 border-primary/20 ml-6 md:ml-12 space-y-8 py-4">
{events.map((event, idx) => (
<div key={idx} className="relative pl-8 md:pl-12 group">
{/* Dot */}
<div
className={`absolute -left-[9px] top-1 h-4 w-4 rounded-full border-2 border-background transition-colors group-hover:scale-110 ${event.type === "birth" ? "bg-primary" : "bg-neutral-400"
}`}
/>
{/* Year Label (Mobile vs Desktop) */}
<div className="absolute -left-16 md:-left-24 top-0 w-12 md:w-20 text-right text-sm font-mono text-muted-foreground font-bold">
{event.year}
</div>
{/* Content Card */}
<div className="bg-card border border-border rounded-lg p-4 shadow-sm transition-all hover:shadow-md hover:border-primary/50 max-w-xl">
<div className="flex items-center justify-between mb-1">
<span
className={`text-xs font-bold px-2 py-0.5 rounded-full ${event.type === "birth" ? "bg-primary/10 text-primary" : "bg-neutral-100 text-neutral-500"
}`}
>
{event.type === "birth" ? "诞生" : "逝世"}
</span>
<span className="text-xs text-muted-foreground font-mono">{event.date}</span>
</div>
<h3 className="text-lg font-serif font-bold">
{event.member.fullName}
<span className="text-sm font-normal text-muted-foreground ml-2">
({event.member.generation})
</span>
</h3>
<p className="text-sm text-muted-foreground mt-1">
{event.type === "birth"
? `出生于 ${event.member.birthPlace || event.member.ancestralHome || "未知地点"}`
: `享年 ${event.year - Number.parseInt(event.member.birthDate?.substring(0, 4) || "0")}`}
</p>
</div>
</div>
))}
{events.length === 0 && (
<div className="pl-8 text-muted-foreground italic"></div>
)}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="migration">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<Card>
<CardHeader>
<CardTitle className="font-serif"> (Records)</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[600px] pr-4">
<div className="space-y-6">
{migrations.map((mig, idx) => (
<div key={idx} className="flex flex-col gap-2 p-4 rounded-lg bg-muted/30 border border-border">
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<span className="font-mono bg-background px-1 rounded">
{mig.year ? `${mig.year}` : "未知年代"}
</span>
<span> {mig.person.generation} </span>
</div>
<div className="flex items-center justify-between gap-4">
<div className="flex-1 text-center p-2 bg-background rounded border border-border/50">
<div className="text-xs text-muted-foreground mb-1"></div>
<div className="font-bold">{mig.from}</div>
<div className="text-xs text-muted-foreground mt-1">{mig.father.fullName}</div>
</div>
<ArrowRight className="text-muted-foreground" />
<div className="flex-1 text-center p-2 bg-background rounded border border-border/50">
<div className="text-xs text-muted-foreground mb-1"></div>
<div className="font-bold text-primary">{mig.to}</div>
<div className="text-xs text-muted-foreground mt-1">{mig.person.fullName}</div>
</div>
</div>
</div>
))}
{migrations.length === 0 && (
<p className="text-center text-muted-foreground py-10">
<br />
/
</p>
)}
</div>
</ScrollArea>
</CardContent>
</Card>
<Card className="bg-neutral-50 dark:bg-neutral-900 border-dashed">
<CardContent className="flex flex-col items-center justify-center h-[600px] text-muted-foreground">
<Map className="h-16 w-16 mb-4 opacity-20" />
<p className="max-w-xs text-center">
<br />
</p>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</main>
</div>
)
}