Files
sfera/src/components/admin/categories-section.tsx

377 lines
15 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 { useQuery, useMutation } from '@apollo/client'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'
import { GET_CATEGORIES } from '@/graphql/queries'
import { CREATE_CATEGORY, UPDATE_CATEGORY, DELETE_CATEGORY } from '@/graphql/mutations'
import { Plus, Edit, Trash2, Package } from 'lucide-react'
import { toast } from 'sonner'
interface Category {
id: string
name: string
createdAt: string
updatedAt: string
}
export function CategoriesSection() {
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [editingCategory, setEditingCategory] = useState<Category | null>(null)
const [newCategoryName, setNewCategoryName] = useState('')
const [editCategoryName, setEditCategoryName] = useState('')
const formatDate = (dateString: string) => {
try {
const date = new Date(dateString)
if (isNaN(date.getTime())) {
return 'Неизвестно'
}
return date.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
} catch (error) {
return 'Неизвестно'
}
}
const { data, loading, error, refetch } = useQuery(GET_CATEGORIES)
const [createCategory, { loading: creating }] = useMutation(CREATE_CATEGORY)
const [updateCategory, { loading: updating }] = useMutation(UPDATE_CATEGORY)
const [deleteCategory, { loading: deleting }] = useMutation(DELETE_CATEGORY)
const categories: Category[] = data?.categories || []
const handleCreateCategory = async () => {
if (!newCategoryName.trim()) {
toast.error('Введите название категории')
return
}
try {
const { data } = await createCategory({
variables: { input: { name: newCategoryName.trim() } }
})
if (data?.createCategory?.success) {
toast.success('Категория успешно создана')
setNewCategoryName('')
setIsCreateDialogOpen(false)
refetch()
} else {
toast.error(data?.createCategory?.message || 'Ошибка при создании категории')
}
} catch (error) {
console.error('Error creating category:', error)
toast.error('Ошибка при создании категории')
}
}
const handleEditCategory = (category: Category) => {
setEditingCategory(category)
setEditCategoryName(category.name)
setIsEditDialogOpen(true)
}
const handleUpdateCategory = async () => {
if (!editingCategory || !editCategoryName.trim()) {
toast.error('Введите название категории')
return
}
try {
const { data } = await updateCategory({
variables: {
id: editingCategory.id,
input: { name: editCategoryName.trim() }
}
})
if (data?.updateCategory?.success) {
toast.success('Категория успешно обновлена')
setEditingCategory(null)
setEditCategoryName('')
setIsEditDialogOpen(false)
refetch()
} else {
toast.error(data?.updateCategory?.message || 'Ошибка при обновлении категории')
}
} catch (error) {
console.error('Error updating category:', error)
toast.error('Ошибка при обновлении категории')
}
}
const handleDeleteCategory = async (categoryId: string) => {
try {
const { data } = await deleteCategory({
variables: { id: categoryId }
})
if (data?.deleteCategory) {
toast.success('Категория успешно удалена')
refetch()
} else {
toast.error('Ошибка при удалении категории')
}
} catch (error) {
console.error('Error deleting category:', error)
const errorMessage = error instanceof Error ? error.message : 'Ошибка при удалении категории'
toast.error(errorMessage)
}
}
const handleCreateBasicCategories = async () => {
const basicCategories = [
'Электроника',
'Одежда',
'Обувь',
'Дом и сад',
'Красота и здоровье',
'Спорт и отдых',
'Автотовары',
'Детские товары',
'Продукты питания',
'Книги и канцелярия'
]
try {
for (const categoryName of basicCategories) {
await createCategory({
variables: { input: { name: categoryName } }
})
}
toast.success('Базовые категории созданы')
refetch()
} catch (error) {
console.error('Error creating basic categories:', error)
toast.error('Ошибка при создании категорий')
}
}
if (loading) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">Категории товаров</h2>
</div>
<Card className="glass-card border-white/10 p-6">
<div className="flex items-center justify-center h-32">
<div className="animate-spin rounded-full h-8 w-8 border-4 border-white border-t-transparent"></div>
</div>
</Card>
</div>
)
}
if (error) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">Категории товаров</h2>
</div>
<Card className="glass-card border-white/10 p-6">
<div className="text-center">
<p className="text-white/70 mb-4">Ошибка загрузки категорий</p>
<Button onClick={() => refetch()} variant="outline" className="bg-white/10 text-white border-white/20">
Попробовать снова
</Button>
</div>
</Card>
</div>
)
}
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white">Категории товаров</h2>
<p className="text-white/70 text-sm">Управление категориями для классификации товаров</p>
</div>
<div className="flex gap-2">
{categories.length === 0 && (
<Button
onClick={handleCreateBasicCategories}
variant="outline"
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
>
Создать базовые категории
</Button>
)}
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white">
<Plus className="w-4 h-4 mr-2" />
Добавить категорию
</Button>
</DialogTrigger>
<DialogContent className="glass-card">
<DialogHeader>
<DialogTitle className="text-white">Создать новую категорию</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="category-name" className="text-white">Название категории</Label>
<Input
id="category-name"
value={newCategoryName}
onChange={(e) => setNewCategoryName(e.target.value)}
placeholder="Введите название..."
className="glass-input text-white placeholder:text-white/50"
onKeyDown={(e) => e.key === 'Enter' && handleCreateCategory()}
/>
</div>
<div className="flex justify-end gap-2">
<Button
variant="outline"
onClick={() => setIsCreateDialogOpen(false)}
className="bg-white/10 text-white border-white/20"
>
Отмена
</Button>
<Button
onClick={handleCreateCategory}
disabled={creating}
className="bg-gradient-to-r from-purple-600 to-pink-600 text-white"
>
{creating ? 'Создание...' : 'Создать'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
<Card className="glass-card border-white/10">
<CardHeader>
<CardTitle className="text-white">Список категорий ({categories.length})</CardTitle>
</CardHeader>
<CardContent>
{categories.length === 0 ? (
<div className="text-center py-12">
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">Нет категорий</h3>
<p className="text-white/60 text-sm mb-4">
Создайте категории для классификации товаров
</p>
<Button
onClick={handleCreateBasicCategories}
className="bg-gradient-to-r from-purple-600 to-pink-600 text-white"
>
Создать базовые категории
</Button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{categories.map((category) => (
<div key={category.id} className="glass-card p-4 border border-white/10 rounded-lg">
<div className="flex items-center justify-between">
<div>
<h4 className="font-medium text-white">{category.name}</h4>
<p className="text-white/60 text-xs">
Создано: {formatDate(category.createdAt)}
</p>
</div>
<div className="flex gap-1">
<Button
size="sm"
variant="outline"
onClick={() => handleEditCategory(category)}
className="bg-white/10 hover:bg-white/20 text-white border-white/20 h-8 w-8 p-0"
>
<Edit className="h-3 w-3" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border-red-500/30 h-8 w-8 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="glass-card">
<AlertDialogHeader>
<AlertDialogTitle className="text-white">Удалить категорию</AlertDialogTitle>
<AlertDialogDescription className="text-white/70">
Вы уверены, что хотите удалить категорию &quot;{category.name}&quot;?
Это действие нельзя отменить. Если в категории есть товары, удаление будет невозможно.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="bg-white/10 text-white border-white/20">
Отмена
</AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDeleteCategory(category.id)}
className="bg-red-600 hover:bg-red-700 text-white"
disabled={deleting}
>
{deleting ? 'Удаление...' : 'Удалить'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
{/* Диалог редактирования */}
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DialogContent className="glass-card">
<DialogHeader>
<DialogTitle className="text-white">Редактировать категорию</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="edit-category-name" className="text-white">Название категории</Label>
<Input
id="edit-category-name"
value={editCategoryName}
onChange={(e) => setEditCategoryName(e.target.value)}
placeholder="Введите название..."
className="glass-input text-white placeholder:text-white/50"
onKeyDown={(e) => e.key === 'Enter' && handleUpdateCategory()}
/>
</div>
<div className="flex justify-end gap-2">
<Button
variant="outline"
onClick={() => setIsEditDialogOpen(false)}
className="bg-white/10 text-white border-white/20"
>
Отмена
</Button>
<Button
onClick={handleUpdateCategory}
disabled={updating}
className="bg-gradient-to-r from-purple-600 to-pink-600 text-white"
>
{updating ? 'Сохранение...' : 'Сохранить'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
}