2025-06-24 10:56:21 +08:00

361 lines
12 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { Book } from '@/lib/types';
import { ArrowLeft, Save } from 'lucide-react';
export default function EditBookPage() {
const params = useParams();
const router = useRouter();
const bookId = params.id as string;
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState({
isbn: '',
title: '',
authors: '',
publisher: '',
publishDate: '',
price: '',
classificationNo: '',
location: '',
totalCopies: '1',
availableCopies: '1',
status: 'normal',
description: '',
coverUrl: '',
});
useEffect(() => {
const fetchBook = async () => {
try {
const response = await fetch(`/api/books/${bookId}`);
if (!response.ok) {
throw new Error('Failed to fetch book');
}
const book: Book = await response.json();
setFormData({
isbn: book.isbn,
title: book.title,
authors: book.authors.join(', '),
publisher: book.publisher || '',
publishDate: book.publishDate || '',
price: book.price || '',
classificationNo: book.classificationNo || '',
location: book.location || '',
totalCopies: book.totalCopies.toString(),
availableCopies: book.availableCopies.toString(),
status: book.status,
description: book.description || '',
coverUrl: book.coverUrl || '',
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
if (bookId) {
fetchBook();
}
}, [bookId]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
try {
const updateData = {
...formData,
authors: formData.authors.split(',').map(author => author.trim()).filter(Boolean),
price: formData.price ? parseFloat(formData.price) : null,
totalCopies: parseInt(formData.totalCopies),
availableCopies: parseInt(formData.availableCopies),
publishDate: formData.publishDate || null,
publisher: formData.publisher || null,
classificationNo: formData.classificationNo || null,
location: formData.location || null,
description: formData.description || null,
coverUrl: formData.coverUrl || null,
};
const response = await fetch(`/api/books/${bookId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData),
});
if (response.ok) {
router.push(`/admin/books/${bookId}`);
} else {
const errorData = await response.json();
alert(`更新失败: ${errorData.error || 'Unknown error'}`);
}
} catch (error) {
console.error('Update error:', error);
alert('更新失败');
} finally {
setSaving(false);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-lg">...</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-red-600">: {error}</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-4xl mx-auto p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
<button
onClick={() => router.back()}
className="flex items-center gap-2 text-gray-600 hover:text-gray-900"
>
<ArrowLeft size={20} />
</button>
<h1 className="text-2xl font-bold text-gray-900"></h1>
</div>
</div>
{/* Form */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
ISBN *
</label>
<input
type="text"
name="isbn"
value={formData.isbn}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
* ()
</label>
<input
type="text"
name="authors"
value={formData.authors}
onChange={handleChange}
required
placeholder="张三, 李四"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
name="publisher"
value={formData.publisher}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="date"
name="publishDate"
value={formData.publishDate}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="number"
name="price"
value={formData.price}
onChange={handleChange}
step="0.01"
min="0"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
name="classificationNo"
value={formData.classificationNo}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
name="location"
value={formData.location}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="number"
name="totalCopies"
value={formData.totalCopies}
onChange={handleChange}
required
min="1"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="number"
name="availableCopies"
value={formData.availableCopies}
onChange={handleChange}
required
min="0"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<select
name="status"
value={formData.status}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="normal"></option>
<option value="damaged"></option>
<option value="removed"></option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="url"
name="coverUrl"
value={formData.coverUrl}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<textarea
name="description"
value={formData.description}
onChange={handleChange}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="flex justify-end gap-4">
<button
type="button"
onClick={() => router.back()}
className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
>
</button>
<button
type="submit"
disabled={saving}
className="flex items-center gap-2 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
<Save size={16} />
{saving ? '保存中...' : '保存'}
</button>
</div>
</form>
</div>
</div>
</div>
);
}