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

110 lines
4.6 KiB
TypeScript

"use client"
import { useState } from "react"
import { SiteHeader } from "@/components/site-header"
import { useFamily } from "@/context/family-context"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Search, Plus, Filter } from "lucide-react"
import Link from "next/link"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { AvatarDisplay } from "@/components/ui/avatar-display"
export default function MembersPage() {
const { treeData, searchResults, searchMembers } = useFamily()
const [query, setQuery] = useState("")
const displayMembers = query
? searchResults
: Object.values(treeData.members).sort(
(a, b) => a.generation - b.generation || (a.birthDate || "").localeCompare(b.birthDate || ""),
)
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="flex flex-col md:flex-row justify-between items-center mb-8 gap-4">
<h1 className="text-3xl font-serif font-bold"></h1>
<div className="flex items-center gap-2 w-full md:w-auto">
<div className="relative flex-1 md:w-80">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索姓名、字号..."
className="pl-9"
value={query}
onChange={(e) => {
setQuery(e.target.value)
searchMembers(e.target.value)
}}
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
<Link href="/members/new">
<Button className="gap-2 bg-primary text-primary-foreground">
<Plus className="h-4 w-4" />
</Button>
</Link>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{displayMembers.map((member) => (
<Link href={`/members/${member.id}`} key={member.id}>
<Card className="hover:border-primary transition-colors cursor-pointer h-full">
<CardContent className="p-6 flex items-start gap-4">
<AvatarDisplay
imageId={member.avatarImageId}
fallbackUrl={member.avatarUrl}
fallbackText={member.surname}
className="h-16 w-16 border-2 border-border"
/>
<div className="flex-1 min-w-0">
<div className="flex items-baseline justify-between">
<h3 className="text-xl font-bold font-serif truncate">
{member.fullName}
{member.courtesyName && (
<span className="text-sm font-normal text-muted-foreground ml-2">
{member.courtesyName}
</span>
)}
</h3>
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded text-muted-foreground whitespace-nowrap">
{member.generation}
</span>
</div>
<p className="text-sm text-muted-foreground mt-1 truncate">
{member.generationName ? `字辈: ${member.generationName}` : "未录入字辈"}
</p>
<p className="text-sm text-muted-foreground truncate">{member.ancestralHome || "籍贯未知"}</p>
<div className="mt-3 flex flex-wrap gap-1">
{member.deathDate && (
<span className="text-[10px] bg-neutral-100 text-neutral-600 px-1.5 py-0.5 rounded border border-neutral-200">
</span>
)}
{member.tags?.map((tag) => (
<span
key={tag}
className="text-[10px] bg-primary/10 text-primary px-1.5 py-0.5 rounded border border-primary/20"
>
{tag}
</span>
))}
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
</main>
</div>
)
}