Добавлены новые функции для управления категориями: реализованы мутации для создания, обновления и удаления категорий. Обновлены компоненты админ-панели для отображения и управления категориями, улучшен интерфейс и адаптивность. Добавлены новые кнопки и обработчики событий для взаимодействия с категориями.

This commit is contained in:
Bivekich
2025-07-19 17:09:40 +03:00
parent 965482b617
commit 8d57fcd748
12 changed files with 1733 additions and 67 deletions

View File

@ -0,0 +1,361 @@
"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 { 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">
Создано: {new Date(category.createdAt).toLocaleDateString('ru-RU')}
</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>
)
}