236 lines
8.4 KiB
TypeScript
236 lines
8.4 KiB
TypeScript
"use client"
|
||
|
||
import Image from 'next/image'
|
||
|
||
import { useMutation } from '@apollo/client'
|
||
import { Card } from '@/components/ui/card'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'
|
||
import { DELETE_PRODUCT } from '@/graphql/mutations'
|
||
import { Edit3, Trash2, Package, Eye, EyeOff } from 'lucide-react'
|
||
import { toast } from 'sonner'
|
||
|
||
interface Product {
|
||
id: string
|
||
name: string
|
||
article: string
|
||
description: string
|
||
price: number
|
||
quantity: number
|
||
type: 'PRODUCT' | 'CONSUMABLE'
|
||
category: { id: string; name: string } | null
|
||
brand: string
|
||
color: string
|
||
size: string
|
||
weight: number
|
||
dimensions: string
|
||
material: string
|
||
images: string[]
|
||
mainImage: string
|
||
isActive: boolean
|
||
createdAt: string
|
||
updatedAt: string
|
||
}
|
||
|
||
interface ProductCardProps {
|
||
product: Product
|
||
onEdit: (product: Product) => void
|
||
onDeleted: () => void
|
||
}
|
||
|
||
export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) {
|
||
const [deleteProduct, { loading: deleting }] = useMutation(DELETE_PRODUCT)
|
||
|
||
const handleDelete = async () => {
|
||
try {
|
||
await deleteProduct({
|
||
variables: { id: product.id }
|
||
})
|
||
toast.success('Товар успешно удален')
|
||
onDeleted()
|
||
} catch (error) {
|
||
console.error('Error deleting product:', error)
|
||
toast.error('Ошибка при удалении товара')
|
||
}
|
||
}
|
||
|
||
const formatPrice = (price: number) => {
|
||
return new Intl.NumberFormat('ru-RU', {
|
||
style: 'currency',
|
||
currency: 'RUB',
|
||
minimumFractionDigits: 0
|
||
}).format(price)
|
||
}
|
||
|
||
const getStatusColor = () => {
|
||
if (!product.isActive) return 'bg-gray-500/20 text-gray-300 border-gray-400/30'
|
||
if (product.quantity === 0) return 'bg-red-500/20 text-red-300 border-red-400/30'
|
||
if (product.quantity < 10) return 'bg-yellow-500/20 text-yellow-300 border-yellow-400/30'
|
||
return 'bg-green-500/20 text-green-300 border-green-400/30'
|
||
}
|
||
|
||
const getStatusText = () => {
|
||
if (!product.isActive) return 'Неактивен'
|
||
if (product.quantity === 0) return 'Нет в наличии'
|
||
if (product.quantity < 10) return 'Мало на складе'
|
||
return 'В наличии'
|
||
}
|
||
|
||
return (
|
||
<Card className="glass-card group relative overflow-hidden transition-all duration-300 hover:scale-[1.02] hover:shadow-xl hover:shadow-purple-500/20">
|
||
{/* Изображение товара */}
|
||
<div className="relative h-48 bg-white/5 overflow-hidden">
|
||
{product.mainImage || product.images[0] ? (
|
||
<Image
|
||
src={product.mainImage || product.images[0]}
|
||
alt={product.name}
|
||
width={300}
|
||
height={200}
|
||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||
/>
|
||
) : (
|
||
<div className="w-full h-full flex items-center justify-center">
|
||
<Package className="h-16 w-16 text-white/30" />
|
||
</div>
|
||
)}
|
||
|
||
{/* Статус товара */}
|
||
<div className="absolute top-2 left-2">
|
||
<Badge className={`text-xs px-2 py-1 ${getStatusColor()}`}>
|
||
{getStatusText()}
|
||
</Badge>
|
||
</div>
|
||
|
||
{/* Индикатор активности */}
|
||
<div className="absolute top-2 right-2">
|
||
{product.isActive ? (
|
||
<Eye className="h-4 w-4 text-green-300" />
|
||
) : (
|
||
<EyeOff className="h-4 w-4 text-gray-400" />
|
||
)}
|
||
</div>
|
||
|
||
{/* Кнопки управления */}
|
||
<div className="absolute bottom-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => onEdit(product)}
|
||
className="p-1 h-7 w-7 bg-white/20 border-white/30 hover:bg-white/30 backdrop-blur"
|
||
>
|
||
<Edit3 className="h-3 w-3 text-white" />
|
||
</Button>
|
||
|
||
<AlertDialog>
|
||
<AlertDialogTrigger asChild>
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
className="p-1 h-7 w-7 bg-red-500/20 border-red-400/30 hover:bg-red-500/30 backdrop-blur"
|
||
>
|
||
<Trash2 className="h-3 w-3 text-white" />
|
||
</Button>
|
||
</AlertDialogTrigger>
|
||
<AlertDialogContent className="glass-card border-white/10">
|
||
<AlertDialogHeader>
|
||
<AlertDialogTitle className="text-white">Удалить товар?</AlertDialogTitle>
|
||
<AlertDialogDescription className="text-white/70">
|
||
Вы уверены, что хотите удалить товар "{product.name}"?
|
||
Это действие нельзя отменить.
|
||
</AlertDialogDescription>
|
||
</AlertDialogHeader>
|
||
<AlertDialogFooter>
|
||
<AlertDialogCancel className="glass-secondary text-white hover:text-white">
|
||
Отмена
|
||
</AlertDialogCancel>
|
||
<AlertDialogAction
|
||
onClick={handleDelete}
|
||
disabled={deleting}
|
||
className="bg-red-600 hover:bg-red-700 text-white"
|
||
>
|
||
{deleting ? 'Удаление...' : 'Удалить'}
|
||
</AlertDialogAction>
|
||
</AlertDialogFooter>
|
||
</AlertDialogContent>
|
||
</AlertDialog>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Информация о товаре */}
|
||
<div className="p-4 space-y-3">
|
||
{/* Название и артикул */}
|
||
<div>
|
||
<h3 className="text-white font-medium text-sm line-clamp-2 leading-tight">
|
||
{product.name}
|
||
</h3>
|
||
<p className="text-white/60 text-xs mt-1">
|
||
Арт. {product.article}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Цена и количество */}
|
||
<div className="flex items-center justify-between">
|
||
<div className="text-white font-semibold">
|
||
{formatPrice(product.price)}
|
||
</div>
|
||
<div className="text-white/70 text-sm">
|
||
{product.quantity} шт.
|
||
</div>
|
||
</div>
|
||
|
||
{/* Дополнительная информация */}
|
||
<div className="space-y-1">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
{/* Тип товара */}
|
||
<Badge
|
||
variant="outline"
|
||
className={`text-xs ${
|
||
product.type === 'PRODUCT'
|
||
? 'bg-blue-500/20 text-blue-300 border-blue-400/30'
|
||
: 'bg-orange-500/20 text-orange-300 border-orange-400/30'
|
||
}`}
|
||
>
|
||
{product.type === 'PRODUCT' ? 'Товар' : 'Расходник'}
|
||
</Badge>
|
||
|
||
{/* Категория */}
|
||
{product.category && (
|
||
<Badge variant="outline" className="glass-secondary text-white/60 border-white/20 text-xs">
|
||
{product.category.name}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex flex-wrap gap-1">
|
||
{product.brand && (
|
||
<span className="text-white/50 text-xs bg-white/10 px-2 py-1 rounded">
|
||
{product.brand}
|
||
</span>
|
||
)}
|
||
{product.color && (
|
||
<span className="text-white/50 text-xs bg-white/10 px-2 py-1 rounded">
|
||
{product.color}
|
||
</span>
|
||
)}
|
||
{product.size && (
|
||
<span className="text-white/50 text-xs bg-white/10 px-2 py-1 rounded">
|
||
{product.size}
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Описание (если есть) */}
|
||
{product.description && (
|
||
<p className="text-white/60 text-xs line-clamp-2 leading-relaxed">
|
||
{product.description}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Эффект градиента при наведении */}
|
||
<div className="absolute inset-0 bg-gradient-to-t from-purple-600/0 via-transparent to-transparent opacity-0 group-hover:opacity-20 transition-opacity duration-300 pointer-events-none" />
|
||
</Card>
|
||
)
|
||
}
|