Добавлены модели товаров и корзины для оптовиков, реализованы соответствующие мутации и запросы в GraphQL. Обновлен API для загрузки файлов с учетом новых типов данных. Улучшена обработка ошибок и добавлены новые функции для работы с категориями товаров.
This commit is contained in:
207
src/components/warehouse/warehouse-dashboard.tsx
Normal file
207
src/components/warehouse/warehouse-dashboard.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { ProductForm } from './product-form'
|
||||
import { ProductCard } from './product-card'
|
||||
import { GET_MY_PRODUCTS } from '@/graphql/queries'
|
||||
import { Plus, Search, Package } from 'lucide-react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
interface Product {
|
||||
id: string
|
||||
name: string
|
||||
article: string
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
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
|
||||
}
|
||||
|
||||
export function WarehouseDashboard() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
const { data, loading, error, refetch } = useQuery(GET_MY_PRODUCTS, {
|
||||
errorPolicy: 'all'
|
||||
})
|
||||
|
||||
const products: Product[] = data?.myProducts || []
|
||||
|
||||
// Фильтрация товаров по поисковому запросу
|
||||
const filteredProducts = products.filter(product =>
|
||||
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.article.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.category?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
const handleCreateProduct = () => {
|
||||
setEditingProduct(null)
|
||||
setIsDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleEditProduct = (product: Product) => {
|
||||
setEditingProduct(product)
|
||||
setIsDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleProductSaved = () => {
|
||||
setIsDialogOpen(false)
|
||||
setEditingProduct(null)
|
||||
refetch()
|
||||
}
|
||||
|
||||
const handleProductDeleted = () => {
|
||||
refetch()
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="h-screen bg-gradient-smooth flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<Card className="flex-1 bg-white/5 backdrop-blur border-white/10 p-6">
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<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">
|
||||
{error.message || 'Не удалось загрузить товары'}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => refetch()}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
Попробовать снова
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-gradient-smooth flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок и поиск */}
|
||||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Склад товаров</h1>
|
||||
<p className="text-white/70 text-sm">Управление ассортиментом вашего склада</p>
|
||||
</div>
|
||||
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={handleCreateProduct}
|
||||
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white border-0 shadow-lg shadow-purple-500/25 transition-all duration-300"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Добавить товар
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="glass-card max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">
|
||||
{editingProduct ? 'Редактировать товар' : 'Добавить новый товар'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ProductForm
|
||||
product={editingProduct}
|
||||
onSave={handleProductSaved}
|
||||
onCancel={() => setIsDialogOpen(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{/* Поиск */}
|
||||
<div className="mb-4 flex-shrink-0">
|
||||
<div className="relative max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/50" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Поиск по названию, артикулу, категории..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="glass-input text-white placeholder:text-white/50 pl-10 h-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основной контент */}
|
||||
<Card className="flex-1 bg-white/5 backdrop-blur border-white/10 p-6 overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-4 border-white border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-white/70">Загрузка товаров...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : filteredProducts.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-white mb-2">
|
||||
{searchQuery ? 'Товары не найдены' : 'Склад пуст'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm mb-4">
|
||||
{searchQuery
|
||||
? 'Попробуйте изменить критерии поиска'
|
||||
: 'Добавьте ваш первый товар на склад'
|
||||
}
|
||||
</p>
|
||||
{!searchQuery && (
|
||||
<Button
|
||||
onClick={handleCreateProduct}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Добавить товар
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredProducts.map((product) => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
onEdit={handleEditProduct}
|
||||
onDeleted={handleProductDeleted}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user