336 lines
13 KiB
TypeScript
336 lines
13 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useState } from "react"
|
|
import type { FamilyMember } from "@/types/family"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
import { CalendarIcon, User, Scroll, Users } from "lucide-react"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { ImageUpload } from "@/components/ui/image-upload"
|
|
|
|
interface MemberFormProps {
|
|
initialData?: Partial<FamilyMember>
|
|
existingMembers?: FamilyMember[]
|
|
onSubmit: (data: FamilyMember) => void
|
|
onCancel: () => void
|
|
}
|
|
|
|
export function MemberForm({ initialData, existingMembers = [], onSubmit, onCancel }: MemberFormProps) {
|
|
// Initialize state with default values or initialData
|
|
const [formData, setFormData] = useState<Partial<FamilyMember>>({
|
|
id: initialData?.id || crypto.randomUUID(),
|
|
surname: initialData?.surname || "李",
|
|
givenName: initialData?.givenName || "",
|
|
fullName: initialData?.fullName || "",
|
|
gender: initialData?.gender || "male",
|
|
generation: initialData?.generation || 1,
|
|
spouseIds: initialData?.spouseIds || [],
|
|
childrenIds: initialData?.childrenIds || [],
|
|
fatherId: initialData?.fatherId,
|
|
motherId: initialData?.motherId,
|
|
...initialData,
|
|
})
|
|
|
|
const handleChange = (field: keyof FamilyMember, value: any) => {
|
|
setFormData((prev) => {
|
|
const newData = { ...prev, [field]: value }
|
|
// Auto-update full name if surname or given name changes
|
|
if (field === "surname" || field === "givenName") {
|
|
newData.fullName = (newData.surname || "") + (newData.givenName || "")
|
|
}
|
|
return newData
|
|
})
|
|
}
|
|
|
|
const handleSpouseChange = (spouseId: string) => {
|
|
// If selecting "none" (empty string), clear spouses
|
|
if (!spouseId) {
|
|
setFormData((prev) => ({ ...prev, spouseIds: [] }))
|
|
return
|
|
}
|
|
// Otherwise set as single spouse (logic can be expanded for multiple later)
|
|
setFormData((prev) => ({ ...prev, spouseIds: [spouseId] }))
|
|
}
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
onSubmit(formData as FamilyMember)
|
|
}
|
|
|
|
const potentialRelatives = existingMembers.filter((m) => m.id !== formData.id)
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-8 max-w-4xl mx-auto p-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Basic Identity */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
|
<User className="h-5 w-5" />
|
|
基本信息 (Identity)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex justify-center mb-4">
|
|
<ImageUpload
|
|
value={formData.avatarImageId}
|
|
onChange={(imageId) => handleChange("avatarImageId", imageId)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="surname">姓 (Surname)</Label>
|
|
<Input
|
|
id="surname"
|
|
value={formData.surname}
|
|
onChange={(e) => handleChange("surname", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="givenName">名 (Given Name)</Label>
|
|
<Input
|
|
id="givenName"
|
|
value={formData.givenName}
|
|
onChange={(e) => handleChange("givenName", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="gender">性别 (Gender)</Label>
|
|
<Select value={formData.gender} onValueChange={(val) => handleChange("gender", val)}>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="male">男 (Male)</SelectItem>
|
|
<SelectItem value="female">女 (Female)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="generation">世系 (Generation)</Label>
|
|
<Input
|
|
id="generation"
|
|
type="number"
|
|
value={formData.generation}
|
|
onChange={(e) => handleChange("generation", Number.parseInt(e.target.value))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Traditional Names */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
|
<Scroll className="h-5 w-5" />
|
|
传统称谓 (Traditional Names)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="courtesyName">字 (Courtesy Name)</Label>
|
|
<Input
|
|
id="courtesyName"
|
|
value={formData.courtesyName || ""}
|
|
onChange={(e) => handleChange("courtesyName", e.target.value)}
|
|
placeholder="e.g. 伯虎"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="artName">号 (Art Name)</Label>
|
|
<Input
|
|
id="artName"
|
|
value={formData.artName || ""}
|
|
onChange={(e) => handleChange("artName", e.target.value)}
|
|
placeholder="e.g. 六如居士"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="generationName">字辈 (Gen. Name)</Label>
|
|
<Input
|
|
id="generationName"
|
|
value={formData.generationName || ""}
|
|
onChange={(e) => handleChange("generationName", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="posthumousName">谥号 (Posthumous)</Label>
|
|
<Input
|
|
id="posthumousName"
|
|
value={formData.posthumousName || ""}
|
|
onChange={(e) => handleChange("posthumousName", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Dates & Places */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
|
<CalendarIcon className="h-5 w-5" />
|
|
生平与地点 (Life & Places)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="birthDate">出生日期 (Birth)</Label>
|
|
<Input
|
|
id="birthDate"
|
|
type="date"
|
|
value={formData.birthDate || ""}
|
|
onChange={(e) => handleChange("birthDate", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="deathDate">逝世日期 (Death)</Label>
|
|
<Input
|
|
id="deathDate"
|
|
type="date"
|
|
value={formData.deathDate || ""}
|
|
onChange={(e) => handleChange("deathDate", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="ancestralHome">籍贯 (Ancestral Home)</Label>
|
|
<Input
|
|
id="ancestralHome"
|
|
value={formData.ancestralHome || ""}
|
|
onChange={(e) => handleChange("ancestralHome", e.target.value)}
|
|
placeholder="e.g. 福建省泉州市"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="burialPlace">葬地 (Burial Place)</Label>
|
|
<Input
|
|
id="burialPlace"
|
|
value={formData.burialPlace || ""}
|
|
onChange={(e) => handleChange("burialPlace", e.target.value)}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Biography */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
|
<Scroll className="h-5 w-5" />
|
|
生平事迹 (Biography)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Textarea
|
|
className="min-h-[200px]"
|
|
placeholder="记录生平事迹..."
|
|
value={formData.bio || ""}
|
|
onChange={(e) => handleChange("bio", e.target.value)}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Relationships */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
|
<Users className="h-5 w-5" />
|
|
家庭关系 (Relationships)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="fatherId">父亲 (Father)</Label>
|
|
<Select
|
|
value={formData.fatherId || "none"}
|
|
onValueChange={(val) => handleChange("fatherId", val === "none" ? undefined : val)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="选择父亲 Select Father" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">无 (None)</SelectItem>
|
|
{potentialRelatives
|
|
.filter((m) => m.gender === "male")
|
|
.map((m) => (
|
|
<SelectItem key={m.id} value={m.id}>
|
|
{m.fullName} ({m.generation}世)
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="motherId">母亲 (Mother)</Label>
|
|
<Select
|
|
value={formData.motherId || "none"}
|
|
onValueChange={(val) => handleChange("motherId", val === "none" ? undefined : val)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="选择母亲 Select Mother" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">无 (None)</SelectItem>
|
|
{potentialRelatives
|
|
.filter((m) => m.gender === "female")
|
|
.map((m) => (
|
|
<SelectItem key={m.id} value={m.id}>
|
|
{m.fullName} ({m.generation}世)
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="spouseId">配偶 (Spouse)</Label>
|
|
<Select value={formData.spouseIds?.[0] || "none"} onValueChange={handleSpouseChange}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="选择配偶 Select Spouse" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">无 (None)</SelectItem>
|
|
{potentialRelatives
|
|
.filter((m) => m.gender !== formData.gender) // Suggest opposite gender
|
|
.map((m) => (
|
|
<SelectItem key={m.id} value={m.id}>
|
|
{m.fullName} ({m.generation}世)
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-4 sticky bottom-4 bg-background/90 p-4 border-t border-border backdrop-blur rounded-lg">
|
|
<Button type="button" variant="outline" onClick={onCancel}>
|
|
取消 (Cancel)
|
|
</Button>
|
|
<Button type="submit" className="bg-primary text-primary-foreground hover:bg-primary/90">
|
|
保存 (Save Member)
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|