Files
chinese-family-tree-2/components/members/story-manager.tsx
T
freedakgmail 00cfae381b 0.0.2.0
2025-11-23 09:24:14 +08:00

212 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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 { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Plus, Edit, Trash, BookOpen, Calendar } from "lucide-react"
import type { FamilyStory } from "@/types/family"
import { v4 as uuidv4 } from "uuid"
import { useDialog } from "@/components/ui/alert-dialog-custom"
interface StoryManagerProps {
stories?: FamilyStory[]
onChange: (stories: FamilyStory[]) => void
readonly?: boolean
}
export function StoryManager({ stories = [], onChange, readonly = false }: StoryManagerProps) {
const { showAlert, showConfirm } = useDialog()
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [editingStory, setEditingStory] = useState<FamilyStory | null>(null)
const [formData, setFormData] = useState<Partial<FamilyStory>>({
title: "",
content: "",
date: "",
})
const handleAdd = () => {
setEditingStory(null)
setFormData({ title: "", content: "", date: "" })
setIsDialogOpen(true)
}
const handleEdit = (story: FamilyStory) => {
setEditingStory(story)
setFormData(story)
setIsDialogOpen(true)
}
const handleDelete = async (storyId: string) => {
const confirmed = await showConfirm("确定要删除这个故事吗?", "删除故事")
if (confirmed) {
onChange(stories.filter(s => s.id !== storyId))
}
}
const handleSave = async () => {
if (!formData.title || !formData.content) {
await showAlert("请填写标题和内容", "提示")
return
}
const now = new Date().toISOString()
if (editingStory) {
// 更新现有故事
onChange(
stories.map(s =>
s.id === editingStory.id
? { ...s, ...formData, updatedAt: now }
: s
)
)
} else {
// 添加新故事
const newStory: FamilyStory = {
id: uuidv4(),
title: formData.title!,
content: formData.content!,
date: formData.date,
imageIds: formData.imageIds || [],
createdAt: now,
updatedAt: now,
}
onChange([...stories, newStory])
}
setIsDialogOpen(false)
setFormData({ title: "", content: "", date: "" })
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium flex items-center gap-2">
<BookOpen className="h-4 w-4" />
({stories.length})
</h3>
{!readonly && (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAdd}
>
<Plus className="mr-2 h-4 w-4" />
</Button>
)}
</div>
{stories.length > 0 ? (
<div className="space-y-4">
{stories
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.map((story) => (
<Card key={story.id} className="hover:shadow-md transition-shadow">
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex-1">
<CardTitle className="text-lg font-serif">{story.title}</CardTitle>
{story.date && (
<div className="flex items-center gap-1 text-sm text-muted-foreground mt-1">
<Calendar className="h-3 w-3" />
{story.date}
</div>
)}
</div>
{!readonly && (
<div className="flex gap-1">
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleEdit(story)}
>
<Edit className="h-3 w-3" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive hover:text-destructive"
onClick={() => handleDelete(story.id)}
>
<Trash className="h-3 w-3" />
</Button>
</div>
)}
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground whitespace-pre-line leading-relaxed">
{story.content}
</p>
<div className="mt-2 text-xs text-muted-foreground">
{new Date(story.createdAt).toLocaleDateString('zh-CN')}
</div>
</CardContent>
</Card>
))}
</div>
) : (
<div className="text-center py-8 text-muted-foreground text-sm border border-dashed rounded-lg">
</div>
)}
{/* 添加/编辑故事对话框 */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{editingStory ? "编辑故事" : "添加故事"}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="story-title"> *</Label>
<Input
id="story-title"
placeholder="例如:爷爷的创业故事"
value={formData.title || ""}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="story-date"></Label>
<Input
id="story-date"
type="date"
value={formData.date || ""}
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="story-content"> *</Label>
<Textarea
id="story-content"
placeholder="记录家族的珍贵故事..."
className="min-h-[200px]"
value={formData.content || ""}
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
</Button>
<Button onClick={handleSave}>
{editingStory ? "保存" : "添加"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}