Files
ckeproekt/app/admin/news/create/page.tsx

371 lines
13 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 React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { ArrowLeft, Save, X } from 'lucide-react';
import Link from 'next/link';
import TextEditor from '@/app/admin/components/TextEditor';
import ImageUpload from '@/app/admin/components/ImageUpload';
const NEWS_CATEGORIES = [
{ id: 'company', name: 'Новости компании' },
{ id: 'promotions', name: 'Акции' },
{ id: 'other', name: 'Другое' }
];
export default function CreateNewsPage() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
title: '',
slug: '',
summary: '',
content: '',
category: 'company',
imageUrl: '',
featured: false,
published: true,
publishedAt: new Date().toISOString().slice(0, 16),
tags: [] as string[]
});
const [tagInput, setTagInput] = useState('');
const generateSlug = (title: string) => {
return title
.toLowerCase()
.replace(/[^a-z0-9а-я]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
};
const handleTitleChange = (title: string) => {
setFormData(prev => ({
...prev,
title,
slug: generateSlug(title)
}));
};
const handleAddTag = () => {
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
setFormData(prev => ({
...prev,
tags: [...prev.tags, tagInput.trim()]
}));
setTagInput('');
}
};
const handleRemoveTag = (tagToRemove: string) => {
setFormData(prev => ({
...prev,
tags: prev.tags.filter(tag => tag !== tagToRemove)
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/news', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...formData,
publishedAt: new Date(formData.publishedAt).toISOString()
})
});
const data = await response.json();
if (data.success) {
router.push('/admin/news');
} else {
console.error('Error creating news:', data.error);
alert('Ошибка при создании новости');
}
} catch (error) {
console.error('Error creating news:', error);
alert('Ошибка при создании новости');
} finally {
setLoading(false);
}
};
const handleSaveAsDraft = async () => {
const draftData = { ...formData, published: false };
setFormData(draftData);
// Сохраняем как черновик
try {
const response = await fetch('/api/news', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...draftData,
publishedAt: new Date(draftData.publishedAt).toISOString()
})
});
const data = await response.json();
if (data.success) {
router.push('/admin/news');
}
} catch (error) {
console.error('Error saving draft:', error);
}
};
return (
<div className="max-w-4xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<Link
href="/admin/news"
className="p-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-md transition-colors"
>
<ArrowLeft className="h-5 w-5" />
</Link>
<div>
<h1 className="text-3xl font-bold text-gray-900">Создать новость</h1>
<p className="text-gray-600 mt-1">Заполните форму для создания новой новости</p>
</div>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Main Content */}
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="space-y-6">
{/* Title */}
<div>
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-2">
Заголовок *
</label>
<input
type="text"
id="title"
value={formData.title}
onChange={(e) => handleTitleChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Введите заголовок новости"
required
/>
</div>
{/* Slug */}
<div>
<label htmlFor="slug" className="block text-sm font-medium text-gray-700 mb-2">
URL (slug) *
</label>
<input
type="text"
id="slug"
value={formData.slug}
onChange={(e) => setFormData(prev => ({ ...prev, slug: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="url-novosti"
required
/>
<p className="text-xs text-gray-500 mt-1">
URL будет: /news/{formData.slug}
</p>
</div>
{/* Summary */}
<div>
<label htmlFor="summary" className="block text-sm font-medium text-gray-700 mb-2">
Краткое описание *
</label>
<textarea
id="summary"
value={formData.summary}
onChange={(e) => setFormData(prev => ({ ...prev, summary: e.target.value }))}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Краткое описание новости для превью"
required
/>
</div>
{/* Content */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Содержание *
</label>
<TextEditor
value={formData.content}
onChange={(content) => setFormData(prev => ({ ...prev, content }))}
placeholder="Введите содержание новости..."
/>
</div>
{/* Image */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Изображение
</label>
<ImageUpload
value={formData.imageUrl}
onChange={(imageUrl) => setFormData(prev => ({ ...prev, imageUrl }))}
onRemove={() => setFormData(prev => ({ ...prev, imageUrl: '' }))}
className="w-full"
/>
</div>
</div>
</div>
{/* Settings */}
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Настройки публикации</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Category */}
<div>
<label htmlFor="category" className="block text-sm font-medium text-gray-700 mb-2">
Категория *
</label>
<select
id="category"
value={formData.category}
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
>
{NEWS_CATEGORIES.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
{/* Publish Date */}
<div>
<label htmlFor="publishedAt" className="block text-sm font-medium text-gray-700 mb-2">
Дата публикации *
</label>
<input
type="datetime-local"
id="publishedAt"
value={formData.publishedAt}
onChange={(e) => setFormData(prev => ({ ...prev, publishedAt: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
</div>
{/* Tags */}
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Теги
</label>
<div className="flex flex-wrap gap-2 mb-2">
{formData.tags.map((tag, index) => (
<span
key={index}
className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
{tag}
<button
type="button"
onClick={() => handleRemoveTag(tag)}
className="ml-1 text-blue-600 hover:text-blue-800"
>
<X className="h-3 w-3" />
</button>
</span>
))}
</div>
<div className="flex space-x-2">
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), handleAddTag())}
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Добавить тег"
/>
<button
type="button"
onClick={handleAddTag}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors"
>
Добавить
</button>
</div>
</div>
{/* Checkboxes */}
<div className="mt-6 space-y-3">
<label className="flex items-center">
<input
type="checkbox"
checked={formData.featured}
onChange={(e) => setFormData(prev => ({ ...prev, featured: e.target.checked }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="ml-2 text-sm text-gray-700">Рекомендуемая новость</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={formData.published}
onChange={(e) => setFormData(prev => ({ ...prev, published: e.target.checked }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="ml-2 text-sm text-gray-700">Опубликовать немедленно</span>
</label>
</div>
</div>
{/* Actions */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm p-6">
<Link
href="/admin/news"
className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors"
>
Отмена
</Link>
<div className="flex space-x-3">
<button
type="button"
onClick={handleSaveAsDraft}
disabled={loading}
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 transition-colors disabled:opacity-50"
>
Сохранить как черновик
</button>
<button
type="submit"
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center"
>
{loading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Создание...
</>
) : (
<>
<Save className="h-4 w-4 mr-2" />
Создать новость
</>
)}
</button>
</div>
</div>
</form>
</div>
);
}