This commit is contained in:
freedakgmail
2025-11-24 14:02:34 +08:00
parent b7a8c9ee6e
commit 3d075c6076
941 changed files with 25613 additions and 27641 deletions
+42 -34
View File
@@ -17,6 +17,7 @@ import { Badge } from "@/components/ui/badge"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import type { FamilyMember } from "@/types/family"
import { permissions } from "@/lib/permissions"
import { MemberNameWithStatus } from "@/components/member-name-with-status"
// 占位符类型
interface PlaceholderMember {
@@ -72,7 +73,7 @@ const MemberCard = memo(function MemberCard({
onLocateInTree: (id: string) => void
}) {
// 判断是否是配偶
const isFounder = member.tags?.includes('始祖')
const isFounder = member.isFounder === true
const isSpouse = !member.fatherId && !member.motherId &&
member.spouseIds && member.spouseIds.length > 0 &&
!isFounder
@@ -85,7 +86,7 @@ const MemberCard = memo(function MemberCard({
return (
<div className="relative group">
<Link href={`/members/${member.id}`}>
<Link href={`/members/${member.id}${typeof window !== 'undefined' ? window.location.search : ''}`}>
<Card className={`hover:border-primary transition-colors cursor-pointer h-full ${
isSpouse
? 'bg-pink-50/50 dark:bg-pink-950/20 border-pink-200/50 dark:border-pink-800/30'
@@ -94,24 +95,21 @@ const MemberCard = memo(function MemberCard({
<CardContent className="p-3 flex items-center gap-5">
<div className="relative flex-shrink-0">
<AvatarDisplay
imageId={member.avatarImageId}
fallbackUrl={member.avatarUrl}
imageUrl={member.avatarUrl}
fallbackText={member.surname}
gender={member.gender}
className="h-20 w-20 border-2 border-border"
/>
{/* 在世/去世标识 */}
{member.deathDate && (
<div className="absolute -bottom-1 -right-1 w-4 h-4 rounded-full bg-gray-500 border-2 border-white" title="已故" />
)}
{!member.deathDate && (
<div className="absolute -bottom-1 -right-1 w-4 h-4 rounded-full bg-green-500 border-2 border-white" title="在世" />
)}
</div>
<div className="flex-1 min-w-0 flex flex-col justify-center">
<div className="flex items-baseline justify-between gap-1.5">
<h3 className="text-lg font-bold font-serif truncate">
{member.fullName}
<h3 className="text-lg font-bold font-serif truncate flex items-center gap-1">
{isFounder && <span className="text-amber-600">👑</span>}
<MemberNameWithStatus
name={member.fullName}
isDead={!!member.deathDate}
dotPosition="top-left"
/>
{member.courtesyName && (
<span className="text-xs font-normal text-muted-foreground ml-1">
{member.courtesyName}
@@ -120,7 +118,7 @@ const MemberCard = memo(function MemberCard({
</h3>
</div>
{isFounder ? (
<p className="text-xs text-amber-700 font-medium mt-0.5 truncate">
<p className="text-xs text-amber-700 font-semibold mt-0.5 truncate">
</p>
) : isSpouse && spouseName ? (
@@ -140,10 +138,17 @@ const MemberCard = memo(function MemberCard({
{/* 生卒年和年龄 */}
{(member.birthDate || member.deathDate) && (
<div className="text-xs text-muted-foreground text-right space-y-0.5 flex-shrink-0">
<div>
{member.birthDate && new Date(member.birthDate).getFullYear()}
{member.deathDate && ` - ${new Date(member.deathDate).getFullYear()}`}
{!member.deathDate && member.birthDate && ' - '}
<div className="flex items-center gap-1 justify-end">
<span>
{member.birthDate && new Date(member.birthDate).getFullYear()}
{member.deathDate && ` - ${new Date(member.deathDate).getFullYear()}`}
{!member.deathDate && member.birthDate && ' - '}
</span>
{member.isLunarDate && (
<span className="text-[10px] text-amber-600 bg-amber-50 px-1 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
{(() => {
if (!member.birthDate) return null
@@ -165,9 +170,11 @@ const MemberCard = memo(function MemberCard({
{member.tags.map((tag) => (
<span
key={tag}
className="text-xs bg-primary/10 text-primary px-2 py-0.5 rounded border border-primary/20"
className={tag === '始祖'
? "text-xs bg-amber-100 text-amber-800 px-2 py-0.5 rounded border border-amber-300 font-semibold"
: "text-xs bg-primary/10 text-primary px-2 py-0.5 rounded border border-primary/20"}
>
{tag}
{tag === '始祖' ? '👑 始祖' : tag}
</span>
))}
</div>
@@ -303,7 +310,7 @@ export default function MembersPage() {
.map(([generation, members]) => {
// 区分家族成员和配偶
const familyMembers = members.filter(m => {
const isFounder = m.tags?.includes('始祖')
const isFounder = m.isFounder === true
const hasFamilyParents = m.fatherId || m.motherId
if (hasFamilyParents || isFounder) return true
@@ -314,8 +321,8 @@ export default function MembersPage() {
// 对家族成员按年龄排序
const sortedFamilyMembers = familyMembers.sort((a, b) => {
const aIsFounder = a.tags?.includes('始祖')
const bIsFounder = b.tags?.includes('始祖')
const aIsFounder = a.isFounder === true
const bIsFounder = b.isFounder === true
if (aIsFounder && !bIsFounder) return -1
if (!aIsFounder && bIsFounder) return 1
@@ -388,7 +395,7 @@ export default function MembersPage() {
)}
</Button>
{permissions.canCreate(currentTree?.currentUserRole) && (
<Link href="/members/new">
<Link href={`/members/new${typeof window !== 'undefined' ? window.location.search : ''}`}>
<Button className="gap-2 bg-primary text-primary-foreground">
<Plus className="h-4 w-4" />
@@ -409,13 +416,13 @@ export default function MembersPage() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="space-y-2">
<Label className="text-xs"> (Surname)</Label>
<Label className="text-xs"></Label>
<Select value={selectedSurname} onValueChange={setSelectedSurname}>
<SelectTrigger>
<SelectValue placeholder="全部" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> (All)</SelectItem>
<SelectItem value="all"></SelectItem>
{uniqueSurnames.map((s) => (
<SelectItem key={s} value={s}>
{s}
@@ -426,13 +433,13 @@ export default function MembersPage() {
</div>
<div className="space-y-2">
<Label className="text-xs"> (Ancestral Home)</Label>
<Label className="text-xs"></Label>
<Select value={selectedHome} onValueChange={setSelectedHome}>
<SelectTrigger>
<SelectValue placeholder="全部" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> (All)</SelectItem>
<SelectItem value="all"></SelectItem>
{uniqueHomes.map((h) => (
<SelectItem key={h} value={h}>
{h}
@@ -443,22 +450,22 @@ export default function MembersPage() {
</div>
<div className="space-y-2">
<Label className="text-xs"> (Status)</Label>
<Label className="text-xs"></Label>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger>
<SelectValue placeholder="全部" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> (All)</SelectItem>
<SelectItem value="living"> (Living)</SelectItem>
<SelectItem value="deceased"> (Deceased)</SelectItem>
<SelectItem value="all"></SelectItem>
<SelectItem value="living"></SelectItem>
<SelectItem value="deceased"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<Label className="text-xs"> (Generation)</Label>
<Label className="text-xs"></Label>
<span className="text-xs text-muted-foreground">
{generationRange[0]} - {generationRange[1]}
</span>
@@ -511,7 +518,8 @@ export default function MembersPage() {
allMembers={allMembers}
onLocateInTree={(id) => {
setHighlightedMemberId(id)
router.push('/tree')
const params = new URLSearchParams(window.location.search)
router.push(`/tree?${params.toString()}`)
}}
/>
)